@pavp/storywright 1.2.0 → 1.4.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/.claude-plugin/plugin.json +6 -0
- package/commands/story-from-figma.md +19 -0
- package/commands/story-generate.md +21 -0
- package/commands/story-refine.md +20 -0
- package/commands/story-split.md +25 -0
- package/package.json +2 -1
- package/scripts/install-skills.mjs +41 -11
- package/scripts/uninstall-skills.mjs +27 -7
- package/skills/story-refine/SKILL.md +12 -0
- package/skills/story-split/SKILL.md +12 -0
|
@@ -21,5 +21,11 @@
|
|
|
21
21
|
"skills/_components/risks-and-dependencies",
|
|
22
22
|
"skills/_components/jira-wiki-formatter"
|
|
23
23
|
],
|
|
24
|
+
"commands": [
|
|
25
|
+
"commands/story-generate.md",
|
|
26
|
+
"commands/story-refine.md",
|
|
27
|
+
"commands/story-split.md",
|
|
28
|
+
"commands/story-from-figma.md"
|
|
29
|
+
],
|
|
24
30
|
"tags": ["product-management", "user-stories", "jira", "agile", "invest"]
|
|
25
31
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Generate user stories from a Figma file or frame URL (uses MCP Figma when available)
|
|
3
|
+
argument-hint: <Figma URL> [+ optional text prompt or screenshots]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Invoke the `story-from-figma` skill from the storywright pack to analyze:
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Follow the skill's procedure:
|
|
11
|
+
|
|
12
|
+
1. Phase 0 — verify MCP Figma server is available. If not, ask the user to either install one, paste PNG exports, or describe the flows.
|
|
13
|
+
2. Phase 0.5 — detect companion text / screenshots. Use text as canonical for `User Story / Scope / Business Goal`; Figma as canonical for `Components / States / Flows`. Surface conflicts as BLOCKING clarifications. Never silently pick a winner.
|
|
14
|
+
3. Phase 1 — inventory pages and frames; group by prototype links into flows.
|
|
15
|
+
4. Phase 2 — per flow: identify goal, entry point, states, components. Score per-inference confidence (HIGH/MEDIUM/LOW). Mark MEDIUM/LOW with `> ⚠️ Assumed:` blockquotes.
|
|
16
|
+
5. Phase 3 — for any flow that fails INVEST (Small), hand off to `/story-split` before generating.
|
|
17
|
+
6. Phase 4 — invoke `story-generate` per flow. Emit dual outputs + `flow-summary.md` mapping stories → frames for traceability.
|
|
18
|
+
|
|
19
|
+
Output in the input language (text language wins if mixed inputs).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Transform an ambiguous prompt, screenshot, or Figma link into a Jira-ready user story
|
|
3
|
+
argument-hint: <prompt | paste story | attach image | figma URL>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Invoke the `story-generate` skill from the storywright pack to handle the following input:
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Follow the skill's full procedure:
|
|
11
|
+
|
|
12
|
+
1. Detect input types present (text / image / figma-link / mixed).
|
|
13
|
+
2. If multi-source, apply the source-priority matrix and surface conflicts as BLOCKING clarifications BEFORE drafting.
|
|
14
|
+
3. Run gap check via `clarification-questions` component. Ask at most 3 critical questions if anything is blocking.
|
|
15
|
+
4. Fill the CORE sections (Title, Summary, User Story, Acceptance Criteria, Definition of Done).
|
|
16
|
+
5. Fill optional sections only if they have real content (drop empty ones).
|
|
17
|
+
6. Run INVEST self-check. If NOT A STORY / NEEDS REFINEMENT / RUN A SPIKE / SPLIT RECOMMENDED — STOP and hand off accordingly.
|
|
18
|
+
7. Render dual outputs: `story.jira-wiki.md` (Jira wiki markup) and `story.standard.md` (CommonMark).
|
|
19
|
+
8. If clarifications remain, emit `clarifications.md` and mark the story DRAFT.
|
|
20
|
+
|
|
21
|
+
Output in the input language (preserve es/en).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Audit an existing user story and fill gaps in place (supports text + optional image / Figma companion)
|
|
3
|
+
argument-hint: <paste existing story> [+ attach image or paste Figma URL for cross-source check]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Invoke the `story-refine` skill from the storywright pack to audit and improve this story:
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Follow the skill's procedure:
|
|
11
|
+
|
|
12
|
+
0. Detect companion sources (image, figma-link). If present, run conflict detection between the story text and the design BEFORE filling sections. Surface conflicts as BLOCKING clarifications; never silently rewrite the story to match the design.
|
|
13
|
+
1. Parse the existing story into the section taxonomy. Mark each section: present / missing / weak.
|
|
14
|
+
2. Gap-check weak sections via `clarification-questions`. Ask only BLOCKING questions.
|
|
15
|
+
3. Detect language and preserve it in output.
|
|
16
|
+
4. Fill missing/weak sections via component skills. Preserve original wording where good.
|
|
17
|
+
5. Append a "Refinement log" at the end listing what changed.
|
|
18
|
+
6. Run INVEST. If verdict is `SPLIT RECOMMENDED`, stop and recommend `/story-split` instead.
|
|
19
|
+
7. Render dual outputs via `jira-wiki-formatter`.
|
|
20
|
+
8. Emit `clarifications.md` if assumptions remain unresolved.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Split an oversize story into an epic + child stories using INVEST + Humanizing Work patterns
|
|
3
|
+
argument-hint: <paste oversize story> [+ optional image / Figma URL for flow inventory]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Invoke the `story-split` skill from the storywright pack to split this story:
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Follow the skill's procedure:
|
|
11
|
+
|
|
12
|
+
0. Detect companion sources (image, figma-link). If present, use them to inventory candidate sub-flows (prototype links, frame structure). If Figma shows N flows but text mentions K (K < N), surface as BLOCKING clarification before drafting the split plan. Text is canonical for scope of the epic; design is canonical for flow structure.
|
|
13
|
+
1. Run `invest-checklist` and apply the pre-split gates:
|
|
14
|
+
- If Valuable FAILS → NOT A STORY. Don't split. Stop.
|
|
15
|
+
- If Testable / Negotiable FAILS → fix in place, don't split. Stop.
|
|
16
|
+
- If Estimable fails on unknowns → recommend a spike. Stop.
|
|
17
|
+
- If Independent / Estimable / Small fails → continue.
|
|
18
|
+
2. Apply the 9-pattern catalog in order. Name the first pattern that fits + the core complexity (meta-pattern).
|
|
19
|
+
3. Calibrate to Cynefin domain (Obvious/Complicated vs Complex vs Chaotic).
|
|
20
|
+
4. Draft a split plan as a Markdown table with rationale, pattern, and proposed children.
|
|
21
|
+
5. Run the strategic evaluation: does the split reveal low-value work? Are children equal-sized?
|
|
22
|
+
6. STOP and ask the user to approve / adjust / cancel. Never auto-split.
|
|
23
|
+
7. After approval: write `epic.md` + one stub per child + `split-plan.md` decision trail.
|
|
24
|
+
8. Coherence check + recursive re-split for children still >1 sprint.
|
|
25
|
+
9. Save artifacts under `./stories/<epic-slug>/`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pavp/storywright",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "PM Skills pack for Claude Code — turn ambiguous inputs (prompts, screenshots, Figma links) into Jira-ready user stories.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"bin/",
|
|
39
39
|
"scripts/",
|
|
40
40
|
"skills/",
|
|
41
|
+
"commands/",
|
|
41
42
|
".claude-plugin/",
|
|
42
43
|
"README.md",
|
|
43
44
|
"LICENSE"
|
|
@@ -1,20 +1,50 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { cp, mkdir, rm } from "node:fs/promises";
|
|
3
|
+
import { cp, mkdir, readdir, rm, unlink } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { SKILLS_DIR, pathExists } from "./lib/skills.mjs";
|
|
5
|
+
import { REPO_ROOT, SKILLS_DIR, pathExists } from "./lib/skills.mjs";
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const skillsTarget = join(homedir(), ".claude", "skills", "storywright");
|
|
8
|
+
const commandsDir = join(homedir(), ".claude", "commands");
|
|
9
|
+
const commandsSource = join(REPO_ROOT, "commands");
|
|
10
|
+
const COMMAND_PREFIX = "storywright-";
|
|
8
11
|
|
|
9
|
-
async function
|
|
10
|
-
if (await pathExists(
|
|
11
|
-
await rm(
|
|
12
|
-
console.log(`• Removed existing ${
|
|
12
|
+
async function installSkills() {
|
|
13
|
+
if (await pathExists(skillsTarget)) {
|
|
14
|
+
await rm(skillsTarget, { recursive: true, force: true });
|
|
15
|
+
console.log(`• Removed existing ${skillsTarget}`);
|
|
16
|
+
}
|
|
17
|
+
await mkdir(skillsTarget, { recursive: true });
|
|
18
|
+
await cp(SKILLS_DIR, skillsTarget, { recursive: true });
|
|
19
|
+
console.log(`✓ Installed skills to ${skillsTarget}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function installCommands() {
|
|
23
|
+
if (!(await pathExists(commandsSource))) {
|
|
24
|
+
console.log("• No commands/ folder in source; skipping slash command install.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
await mkdir(commandsDir, { recursive: true });
|
|
28
|
+
|
|
29
|
+
// Remove old storywright-* commands so renames don't leave orphans.
|
|
30
|
+
const existing = await readdir(commandsDir).catch(() => []);
|
|
31
|
+
for (const f of existing) {
|
|
32
|
+
if (f.startsWith(COMMAND_PREFIX)) await unlink(join(commandsDir, f));
|
|
13
33
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
34
|
+
|
|
35
|
+
const sources = await readdir(commandsSource);
|
|
36
|
+
for (const f of sources) {
|
|
37
|
+
if (!f.endsWith(".md")) continue;
|
|
38
|
+
await cp(join(commandsSource, f), join(commandsDir, `${COMMAND_PREFIX}${f}`));
|
|
39
|
+
}
|
|
40
|
+
console.log(`✓ Installed slash commands to ${commandsDir} (prefix: ${COMMAND_PREFIX})`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
await installSkills();
|
|
45
|
+
await installCommands();
|
|
46
|
+
console.log(` Restart Claude Code for changes to be picked up.`);
|
|
47
|
+
console.log(` After restart, try: /storywright-story-generate <your prompt>`);
|
|
18
48
|
}
|
|
19
49
|
|
|
20
50
|
main().catch((err) => {
|
|
@@ -1,18 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { rm } from "node:fs/promises";
|
|
3
|
+
import { readdir, rm, unlink } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { pathExists } from "./lib/skills.mjs";
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const skillsTarget = join(homedir(), ".claude", "skills", "storywright");
|
|
8
|
+
const commandsDir = join(homedir(), ".claude", "commands");
|
|
9
|
+
const COMMAND_PREFIX = "storywright-";
|
|
8
10
|
|
|
9
11
|
async function main() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
let removed = 0;
|
|
13
|
+
|
|
14
|
+
if (await pathExists(skillsTarget)) {
|
|
15
|
+
await rm(skillsTarget, { recursive: true, force: true });
|
|
16
|
+
console.log(`✓ Removed ${skillsTarget}`);
|
|
17
|
+
removed++;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (await pathExists(commandsDir)) {
|
|
21
|
+
const files = await readdir(commandsDir);
|
|
22
|
+
let cmdsRemoved = 0;
|
|
23
|
+
for (const f of files) {
|
|
24
|
+
if (f.startsWith(COMMAND_PREFIX)) {
|
|
25
|
+
await unlink(join(commandsDir, f));
|
|
26
|
+
cmdsRemoved++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (cmdsRemoved > 0) {
|
|
30
|
+
console.log(`✓ Removed ${cmdsRemoved} slash commands from ${commandsDir}`);
|
|
31
|
+
removed++;
|
|
32
|
+
}
|
|
13
33
|
}
|
|
14
|
-
|
|
15
|
-
console.log(
|
|
34
|
+
|
|
35
|
+
if (removed === 0) console.log("• Nothing to uninstall.");
|
|
16
36
|
}
|
|
17
37
|
|
|
18
38
|
main().catch((err) => {
|
|
@@ -6,6 +6,8 @@ intent: Refinement skill for stories that already exist but are incomplete or we
|
|
|
6
6
|
version: 1.0.0
|
|
7
7
|
inputs:
|
|
8
8
|
- text
|
|
9
|
+
- image
|
|
10
|
+
- figma-link
|
|
9
11
|
outputs:
|
|
10
12
|
- story.jira-wiki.md
|
|
11
13
|
- story.standard.md
|
|
@@ -37,9 +39,19 @@ For oversized stories that fail `Independent / Estimable / Small`, hand off to `
|
|
|
37
39
|
## Inputs & interpretation
|
|
38
40
|
|
|
39
41
|
- **text** — existing story. Detect which sections are present, which are missing, which are weak.
|
|
42
|
+
- **image (optional)** — companion screenshot/mockup the story references. Use to validate UI claims and surface missing edge cases / states.
|
|
43
|
+
- **figma-link (optional)** — companion design. Use to enrich Technical Considerations, Edge Cases (states shown but not in story), and to detect scope mismatches.
|
|
44
|
+
|
|
45
|
+
### Mixed inputs
|
|
46
|
+
|
|
47
|
+
When the user pastes a story plus an image / Figma link, apply the source-priority matrix from `[[story-generate]]` "Mixed inputs" section:
|
|
48
|
+
- Story text remains canonical for `User Story / Scope / Business Goal`.
|
|
49
|
+
- Image/Figma is canonical for `Components / States / Edge Cases / UX flow`.
|
|
50
|
+
- Surface conflicts as BLOCKING clarifications (e.g., story says "single provider" but Figma shows multiple). Never silently rewrite the story to match the design without asking.
|
|
40
51
|
|
|
41
52
|
## Application (step-by-step)
|
|
42
53
|
|
|
54
|
+
0. **Detect companion sources** (image, figma-link). If present, run conflict detection against the story text BEFORE filling sections. Add detected conflicts to the gap-check output.
|
|
43
55
|
1. **Parse the existing story.** Map content into the 15-section taxonomy. Note: present / missing / weak.
|
|
44
56
|
2. **Gap-check the weak sections** via `[[clarification-questions]]`. If gaps are inferrable, mark `⚠️ Assumed` and proceed. Only ask BLOCKING questions.
|
|
45
57
|
3. **Detect language** of the existing story; preserve it in the output.
|
|
@@ -6,6 +6,8 @@ intent: Splitting skill that uses the INVEST failure reasons (from invest-checkl
|
|
|
6
6
|
version: 1.0.0
|
|
7
7
|
inputs:
|
|
8
8
|
- text
|
|
9
|
+
- image
|
|
10
|
+
- figma-link
|
|
9
11
|
outputs:
|
|
10
12
|
- split-plan.md
|
|
11
13
|
- epic.md
|
|
@@ -28,6 +30,16 @@ When a story is an epic in disguise, splitting badly is worse than not splitting
|
|
|
28
30
|
## Inputs & interpretation
|
|
29
31
|
|
|
30
32
|
- **text** — the oversize story (or a one-line goal that's clearly epic-scoped).
|
|
33
|
+
- **image (optional)** — companion mockup. Use to validate scope and reveal hidden sub-flows the text doesn't mention.
|
|
34
|
+
- **figma-link (optional)** — companion design. Often makes splitting easier: prototype links and frame structure reveal natural flow boundaries that map to children.
|
|
35
|
+
|
|
36
|
+
### Mixed inputs
|
|
37
|
+
|
|
38
|
+
When the user provides text + image / Figma alongside the story:
|
|
39
|
+
- **Text is canonical for User Story / Scope / Business Goal** of the epic.
|
|
40
|
+
- **Figma / image is canonical for flow structure** — use prototype links to enumerate candidate sub-flows. Each flow is a candidate child story.
|
|
41
|
+
- **Conflict handling:** if Figma shows N flows but text only mentions K (K < N), surface this in the gap-check via `[[clarification-questions]]` BEFORE drafting the split plan. Ask: "Figma includes flows for X, Y, Z — include in this epic, or scope out?" Never silently expand or shrink scope.
|
|
42
|
+
- See `[[story-generate]]` "Mixed inputs" for the full source-priority matrix.
|
|
31
43
|
|
|
32
44
|
## Application (step-by-step)
|
|
33
45
|
|