@pavp/storywright 1.2.0 → 1.3.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 +19 -0
- package/commands/story-split.md +24 -0
- package/package.json +2 -1
- package/scripts/install-skills.mjs +41 -11
- package/scripts/uninstall-skills.mjs +27 -7
|
@@ -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,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Audit an existing user story and fill gaps in place
|
|
3
|
+
argument-hint: <paste existing story>
|
|
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
|
+
1. Parse the existing story into the section taxonomy. Mark each section: present / missing / weak.
|
|
13
|
+
2. Gap-check weak sections via `clarification-questions`. Ask only BLOCKING questions.
|
|
14
|
+
3. Detect language and preserve it in output.
|
|
15
|
+
4. Fill missing/weak sections via component skills. Preserve original wording where good.
|
|
16
|
+
5. Append a "Refinement log" at the end listing what changed.
|
|
17
|
+
6. Run INVEST. If verdict is `SPLIT RECOMMENDED`, stop and recommend `/story-split` instead.
|
|
18
|
+
7. Render dual outputs via `jira-wiki-formatter`.
|
|
19
|
+
8. Emit `clarifications.md` if assumptions remain unresolved.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Split an oversize story into an epic + child stories using INVEST + Humanizing Work patterns
|
|
3
|
+
argument-hint: <paste oversize story>
|
|
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
|
+
1. Run `invest-checklist` and apply the pre-split gates:
|
|
13
|
+
- If Valuable FAILS → NOT A STORY. Don't split. Stop.
|
|
14
|
+
- If Testable / Negotiable FAILS → fix in place, don't split. Stop.
|
|
15
|
+
- If Estimable fails on unknowns → recommend a spike. Stop.
|
|
16
|
+
- If Independent / Estimable / Small fails → continue.
|
|
17
|
+
2. Apply the 9-pattern catalog in order. Name the first pattern that fits + the core complexity (meta-pattern).
|
|
18
|
+
3. Calibrate to Cynefin domain (Obvious/Complicated vs Complex vs Chaotic).
|
|
19
|
+
4. Draft a split plan as a Markdown table with rationale, pattern, and proposed children.
|
|
20
|
+
5. Run the strategic evaluation: does the split reveal low-value work? Are children equal-sized?
|
|
21
|
+
6. STOP and ask the user to approve / adjust / cancel. Never auto-split.
|
|
22
|
+
7. After approval: write `epic.md` + one stub per child + `split-plan.md` decision trail.
|
|
23
|
+
8. Coherence check + recursive re-split for children still >1 sprint.
|
|
24
|
+
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.3.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) => {
|