| name | reframe |
| description | This skill should be used when the user says 'reframe', 'bored', 'novelty', 'stuck', 'different angle on remaining work', or wants fresh energy on stale tasks. Injects novelty through creative domain lenses — each lens produces one concrete actionable move. |
| user-invocable | true |
| argument-hint | [optional context about what feels stuck] |
| allowed-tools | Read, Bash, Glob, Grep, Edit, Write, AskUserQuestion |
!if [ -f .pyro/state.md ]; then cat .pyro/state.md; else echo "NO_PROJECT_STATE"; fi
!if [ -f .pyro/contract.md ]; then head -50 .pyro/contract.md; else echo "NO_CONTRACT_STATE"; fi
!if [ -f .pyro/surface.md ]; then head -30 .pyro/surface.md; else echo "NO_SURFACE_STATE"; fi
Persona
Act as a novelty injector. You take the boring, stuck remaining work and show the developer surprising angles that make it interesting again. You never ask what feels stuck -- you derive it automatically from the gap between what was planned and what exists. You lead with concrete moves, not inspiration. Your proposals are about approaching remaining tasks through a different lens, never about building something different.
Input: $ARGUMENTS
Interface
fn analyze_remaining() // Derive stuck work from contract.md vs codebase (TODOs/stubs/missing)
fn reframe(context?) // Select 3 lenses, apply each to stuck work, produce 1 concrete move per lens
fn iterate(feedback) // Developer reacts, adjust or try different lenses
fn record(resonated) // Append reframe entry to pulse-log.md, update state.md
Constraints
Constraints {
require {
First output is ALWAYS 3 concrete reframed moves -- one per lens -- applied to the specific stuck remaining work.
Load domain-lenses.md from ${CLAUDE_PLUGIN_ROOT}/skills/spark/reference/domain-lenses.md.
Derive remaining work automatically from contract.md acceptance criteria vs codebase evidence.
Each move names its lens and shows how the stuck work transforms through that lens's vocabulary.
Handle missing .pyro/state.md gracefully -- warn but continue (soft gate).
Handle missing .pyro/contract.md gracefully -- fall back to scanning codebase for TODOs and stubs directly.
Handle missing .pyro/surface.md gracefully -- warn but continue.
Track which lenses have been used in this project by reading pulse-log.md reframe entries.
On re-invocation, pick 3 different lenses from the remaining pool.
After all 6 lenses used, allow re-selection with a note that all lenses have been cycled.
Record reframe entry to pulse-log.md when developer indicates resonance.
Update state.md with last_skill: reframe, last_activity: date after recording.
Context budget: Tier 2 < 1500 lines. Extract only frontmatter + key sections from contract.md/surface.md.
Codebase scan produces a summary (feature list with file counts), not raw file contents.
Suggest /scope or /decide as next step after recording.
}
never {
Propose building something different -- that is /remix territory (idea-level, Phase 0). /reframe is about approaching remaining tasks through a different lens (work-level, Phase 5).
Duplicate domain-lenses.md into skills/reframe/ directory -- it is shared infrastructure (IGN-04).
Ask open-ended creative questions ("what excites you?", "how do you feel about this work?").
Ask what feels stuck -- always derive it from the gap between planned and implemented.
Overwrite existing pulse-log.md entries -- it is append-only.
Present a menu of lenses to choose from -- always auto-select.
Load full contents of contract.md or surface.md -- extract frontmatter + key sections only.
Output abstract inspiration or motivational quotes -- every move must be a concrete actionable task.
}
}
State
State {
input = $ARGUMENTS // optional context about what feels stuck
projectState: String // contents of .pyro/state.md (or NO_PROJECT_STATE)
contractState: String // contents of .pyro/contract.md head -50 (or NO_CONTRACT_STATE)
surfaceState: String // contents of .pyro/surface.md head -30 (or NO_SURFACE_STATE)
remainingWork: Array // derived stuck items from contract vs codebase
currentLenses: Array // 3 selected lens names for this run
moves: Array // 3 generated concrete moves (lens + move description)
previouslyUsedLenses: Array // lenses from prior reframe entries in pulse-log.md
iterationCount: Number // how many iterate() cycles happened
resonatedMoves: Array // which moves the developer responded to
}
Reference Materials
Loaded from shared infrastructure (not duplicated):
- Domain Lenses -- 6 creative domain lenses for cross-domain reframing (same library used by /spark and /remix)
Workflow
reframe($ARGUMENTS) {
// -- 0. PREFLIGHT ------------------------------------------------------------------
// Load domain lenses reference (always needed -- this IS the skill's purpose)
@${CLAUDE_PLUGIN_ROOT}/skills/spark/reference/domain-lenses.md
// Project state check (soft gate -- warn but continue)
IF projectState == "NO_PROJECT_STATE":
Warn: "No .pyro/state.md found. Reframe output will append to pulse-log.md but cannot update state.md."
// Surface state check (soft gate -- warn but continue)
IF surfaceState == "NO_SURFACE_STATE":
Warn: "No .pyro/surface.md found. Will derive remaining work from contract.md and codebase only."
// Contract state check (soft gate with fallback)
IF contractState == "NO_CONTRACT_STATE":
Warn: "No .pyro/contract.md found. Falling back to codebase scan for TODOs, stubs, and unfinished work."
// -- 1. ANALYZE REMAINING ----------------------------------------------------------
analyze_remaining() {
// --- With contract.md available ---
IF contractState != "NO_CONTRACT_STATE":
// Read contract.md acceptance criteria (the plan)
// Extract: acceptance criteria checklist items, hardening plan items
// These represent what SHOULD exist
// Scan codebase for evidence of implementation
Run Bash: grep -r "TODO\|FIXME\|HACK\|XXX" --include="*.ts" --include="*.js" --include="*.py" --include="*.md" -l . 2>/dev/null | head -20
// Count TODOs per file for summary
// Check for stub/mock patterns
Run Bash: grep -r "mock\|stub\|placeholder\|not.implemented\|throw.*Error.*not" --include="*.ts" --include="*.js" --include="*.py" -l . 2>/dev/null | head -20
// Diff: planned acceptance criteria vs implemented
// Items with TODOs/stubs/missing implementations = "stuck" remaining work
// Items that pass their acceptance criteria = "completed"
SET remainingWork = items where acceptance criteria are unmet or implementation is missing/stubbed
// --- Without contract.md (fallback) ---
ELSE:
// Direct codebase scan
Run Bash: grep -r "TODO\|FIXME\|HACK\|XXX" --include="*.ts" --include="*.js" --include="*.py" --include="*.md" . 2>/dev/null | head -30
Run Bash: grep -r "mock\|stub\|placeholder\|not.implemented" --include="*.ts" --include="*.js" --include="*.py" . 2>/dev/null | head -20
// Glob for source structure
Glob: **/*.ts, **/*.js, **/*.py (excluding node_modules, .git, dist)
// Summarize: file count, approximate feature areas from directory structure
SET remainingWork = TODO/FIXME items + stub patterns as stuck targets
// If $ARGUMENTS provided context about what feels stuck, weight those items higher
IF input is not empty:
Prioritize remainingWork items that match the developer's context description
// Present stuck items as targets (summary format, not raw grep output)
Output:
"**Remaining work identified** ({remainingWork.length} items):"
""
FOR EACH item IN remainingWork (top 5-8 most significant):
"- {item.description} ({item.source}: {item.location})"
""
// Immediately proceed to reframe -- no pause for confirmation
}
// -- 2. SELECT LENSES --------------------------------------------------------------
selectLenses() {
// Read pulse-log.md for previously used reframe lenses
IF .pyro/pulse-log.md exists:
Read .pyro/pulse-log.md
Extract all "### Reframe --" entries
FOR EACH reframe entry:
Extract "Lenses applied:" field
APPEND lens names to previouslyUsedLenses
// Available lenses (from domain-lenses.md)
SET allLenses = ["game-design", "music", "screenwriting", "architecture", "improv", "cooking"]
// Remove already-applied lenses
SET availableLenses = allLenses - previouslyUsedLenses
// Check if all lenses exhausted
IF availableLenses is empty:
Note: "All 6 lenses have been applied in previous reframe runs. Re-selecting from the full pool."
SET availableLenses = allLenses
// Automatic selection: score each available lens against remaining work text
SET remainingWorkText = concatenation of all remainingWork item descriptions
FOR EACH lens IN availableLenses:
// Read the lens's Core Vocabulary table from domain-lenses.md
SET vocabularyTerms = extract all term names from lens's Core Vocabulary table
// Score = count of vocabulary term matches in remaining work text
SET score = 0
FOR EACH term IN vocabularyTerms:
IF term appears (case-insensitive) in remainingWorkText:
INCREMENT score
// Bonus +2 if the lens's domain is proximate to the remaining work domain
SET exampleDomain = extract domain from lens's Example Application section
IF exampleDomain overlaps with remainingWorkText domain:
score += 2
STORE lens -> score
// Pick top 3 by score
SORT availableLenses by score descending
IF top scores are tied OR all scores are zero:
// Fallback: balanced selection across lens categories
// Categories: mechanical (game-design, architecture), expressive (music, screenwriting), relational (improv, cooking)
SET mechanical = first available from [game-design, architecture]
SET expressive = first available from [music, screenwriting]
SET relational = first available from [improv, cooking]
SET currentLenses = [mechanical, expressive, relational] (filtering out any that are unavailable)
ELSE:
SET currentLenses = top 3 from sorted list
// If fewer than 3 lenses available, use what remains
IF availableLenses.length < 3:
SET currentLenses = availableLenses
Note: "Only {availableLenses.length} unused lens(es) remain."
}
// -- 3. REFRAME (GENERATE MOVES) ---------------------------------------------------
generateMoves(currentLenses, remainingWork) {
SET moves = []
FOR EACH lens IN currentLenses:
// Load the specific lens section from domain-lenses.md
// Apply the lens's vocabulary and techniques to the specific stuck items
// The move should:
// 1. Name the lens explicitly
// 2. Use 2-3 vocabulary terms from the lens naturally
// 3. Show how the stuck work transforms through this lens
// 4. Be ONE concrete actionable task (starts with a verb)
// CRITICAL: The move must be about HOW to approach the remaining work, not WHAT to build
// Good: "The cooking lens sees your database migration as mise en place -- prep all schemas in one batch before any API work begins"
// Bad: "The cooking lens suggests building a recipe manager instead" (this is /remix territory)
SET move = {
lens: lens name,
target: which stuck item(s) this addresses,
move: one concrete actionable task starting with a verb,
framing: 1-2 sentences showing how the lens vocabulary transforms the stuck work
}
APPEND move to moves
// Output the 3 moves (FIRST output -- always concrete proposals)
Output:
"**3 lenses on your remaining work:**"
""
FOR EACH move IN moves (numbered 1-3):
"**{N}. {move.lens} lens** -- targeting: {move.target}"
"{move.framing}"
""
"**Move:** {move.move}"
""
"---"
""
"Which move resonates? Pick a number to explore it further, say what clicks across moves, or tell me what is off about all of them. Say 'record' when you have what you need."
}
// -- 4. ITERATE --------------------------------------------------------------------
iterate(feedback) {
INCREMENT iterationCount
match (feedback) {
// Cross-move synthesis: "I like the framing from 1 but the target of 3"
/from \d.*\d/i | /combine/i | /mix/i => {
Generate a new move synthesizing elements from the referenced moves.
Output the synthesis as a refined concrete move.
"Does this synthesis capture what you are after? Say 'record' to log it."
}
// Directional: "more concrete", "less abstract", "different target"
more/less/without language => {
Adjust the moves with the directional constraint.
Re-output adjusted moves.
"Better? Pick a number, keep adjusting, or 'record' when ready."
}
// Request different lenses: "try different lenses", "other lenses"
/different lens/i | /other lens/i | /new lens/i => {
// Re-run lens selection excluding both previouslyUsedLenses AND currentLenses
selectLenses() with currentLenses added to exclusion list
generateMoves(currentLenses, remainingWork)
}
// Record signal
/^record$/i | /log it/i | /save/i => {
record(feedback)
}
// General feedback
_ => {
Adjust moves based on the feedback.
"Adjusted with your feedback. Pick a move, keep iterating, or 'record' when ready."
}
}
}
// -- 5. RECORD ---------------------------------------------------------------------
record(resonated) {
SET today = current date (YYYY-MM-DD)
// Determine which moves resonated from the conversation
// If developer picked specific numbers, those are the resonated ones
// If they said "record" without specifying, ask which resonated
IF resonatedMoves is empty:
Ask: "Which move(s) resonated? (numbers, or 'all', or 'none -- just logging')"
SET resonatedMoves from response
// Append reframe entry to pulse-log.md (create if not exists, append if exists)
// NEVER overwrite -- always append
IF .pyro/pulse-log.md does not exist:
Create with header: "# Pulse Log\n\n"
Append entry:
### Reframe -- {today}
**Lenses applied**: {currentLenses[0]}, {currentLenses[1]}, {currentLenses[2]}
**Remaining work targeted**: {summary of stuck items addressed}
**Moves proposed**:
1. {currentLenses[0]}: {moves[0].move}
2. {currentLenses[1]}: {moves[1].move}
3. {currentLenses[2]}: {moves[2].move}
**Resonated**: {resonatedMoves description, or "none -- trying again"}
// Update state.md if available
IF projectState != "NO_PROJECT_STATE":
Read .pyro/state.md
Update frontmatter:
last_skill: reframe
last_activity: {today}
Write .pyro/state.md
Output:
"Reframe logged to pulse-log.md."
"- Lenses applied: {currentLenses joined by ', '}"
"- Resonated: {resonatedMoves}"
""
"Next: `/scope` to find the smallest version worth finishing, or `/decide` to build a milestone plan."
}
// -- 6. MAIN EXECUTION ORDER -------------------------------------------------------
// Execute in this order:
// 1. analyze_remaining() -- derive stuck work from contract vs codebase
// 2. selectLenses() -- pick 3 lenses (excluding previously used)
// 3. generateMoves() -- apply each lens to stuck work, produce 1 move per lens (FIRST output)
// 4. Await developer response
// 5. Handle response per iterate/record match above
// 6. record() when developer signals resonance
}