@windyroad/jtbd 0.1.1
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 +5 -0
- package/agents/agent.md +99 -0
- package/bin/install.mjs +42 -0
- package/hooks/hooks.json +16 -0
- package/hooks/jtbd-enforce-edit.sh +58 -0
- package/hooks/jtbd-eval.sh +35 -0
- package/hooks/jtbd-mark-reviewed.sh +50 -0
- package/hooks/jtbd-reset-marker.sh +16 -0
- package/hooks/lib/review-gate.sh +102 -0
- package/lib/install-utils.mjs +163 -0
- package/package.json +29 -0
- package/skills/wr:jtbd/SKILL.md +96 -0
package/agents/agent.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent
|
|
3
|
+
description: Jobs To Be Done reviewer. Use before editing any user-facing UI files.
|
|
4
|
+
Reads docs/JOBS_TO_BE_DONE.md and PRODUCT_DISCOVERY.md and reviews proposed changes
|
|
5
|
+
against documented jobs, persona constraints, and screen mappings. Reports alignment
|
|
6
|
+
or gaps.
|
|
7
|
+
tools:
|
|
8
|
+
- Read
|
|
9
|
+
- Glob
|
|
10
|
+
- Grep
|
|
11
|
+
- Bash
|
|
12
|
+
model: inherit
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
You are the JTBD Lead. You review proposed UI changes against the project's Jobs To Be Done documentation and persona definitions before any user-facing code is edited. You are a reviewer, not an editor.
|
|
16
|
+
|
|
17
|
+
## Your Role
|
|
18
|
+
|
|
19
|
+
1. Read `docs/jtbd/README.md` for the index of all personas and jobs
|
|
20
|
+
2. Read the relevant persona and job files matching the area being edited
|
|
21
|
+
3. Read the file(s) being edited to understand what user flow is changing
|
|
22
|
+
4. Review proposed changes against every documented job and the persona
|
|
23
|
+
5. Report: PASS if aligned, or list specific misalignments and gaps
|
|
24
|
+
|
|
25
|
+
## What You Check
|
|
26
|
+
|
|
27
|
+
All review criteria come from the JTBD documentation. Read the docs first and apply them. Typical checks include:
|
|
28
|
+
|
|
29
|
+
### Job Alignment
|
|
30
|
+
- Does the change serve a documented job? Match the change to a specific job ID
|
|
31
|
+
- If the change introduces a new user flow not covered by any job, flag it as a job gap
|
|
32
|
+
- If the change contradicts the intent of a documented job, flag it as a misalignment
|
|
33
|
+
|
|
34
|
+
### Persona Fit
|
|
35
|
+
- Read the persona definitions from the JTBD docs
|
|
36
|
+
- Check the change against the primary persona's context constraints as documented
|
|
37
|
+
- Flag changes that conflict with documented persona needs
|
|
38
|
+
|
|
39
|
+
### Screen Mapping
|
|
40
|
+
- Is the file being edited mapped to a specific job in the Job-to-Screen Mapping table?
|
|
41
|
+
- If adding a new route or page, does it have a corresponding job documented?
|
|
42
|
+
- Are `// @jtbd` annotations present and correct?
|
|
43
|
+
|
|
44
|
+
### API / Action Alignment
|
|
45
|
+
- If the change involves API interactions, do the actions align with the job's expected flow?
|
|
46
|
+
- Are new actions documented in the relevant job's action list?
|
|
47
|
+
|
|
48
|
+
## How to Report
|
|
49
|
+
|
|
50
|
+
If the change aligns with documented jobs:
|
|
51
|
+
> **JTBD Review: PASS**
|
|
52
|
+
> Change serves job: `[job-id]` — [brief alignment summary]
|
|
53
|
+
> Persona fit: confirmed — [which constraints were checked]
|
|
54
|
+
|
|
55
|
+
If there are misalignments or gaps:
|
|
56
|
+
|
|
57
|
+
> **JTBD Review: ISSUES FOUND**
|
|
58
|
+
>
|
|
59
|
+
> 1. **[Job Gap / Persona Mismatch / Missing Annotation]**
|
|
60
|
+
> - **File**: `path`, Line ~N
|
|
61
|
+
> - **Issue**: What is misaligned
|
|
62
|
+
> - **Job**: Which job is affected (or "no matching job")
|
|
63
|
+
> - **Fix**: Suggested resolution (update JTBD doc, adjust UI, add annotation)
|
|
64
|
+
>
|
|
65
|
+
> 2. ...
|
|
66
|
+
|
|
67
|
+
## Guide Gap Detection
|
|
68
|
+
|
|
69
|
+
If the code introduces a user flow, screen, or interaction not covered by the JTBD docs, flag this as a job gap:
|
|
70
|
+
|
|
71
|
+
> **JTBD Review: JOB UPDATE NEEDED**
|
|
72
|
+
>
|
|
73
|
+
> The code introduces [flow/screen/interaction] which is not covered by any documented job.
|
|
74
|
+
> Recommended addition to JTBD docs: [specific job definition to add]
|
|
75
|
+
|
|
76
|
+
If the code serves a user type not described by the existing persona:
|
|
77
|
+
|
|
78
|
+
> **JTBD Review: PERSONA UPDATE NEEDED**
|
|
79
|
+
>
|
|
80
|
+
> The code serves [user type/context] which is not described by the current persona.
|
|
81
|
+
> Recommended update to persona docs: [specific persona attributes to add]
|
|
82
|
+
|
|
83
|
+
These are FAIL verdicts — the JTBD documentation must be updated before the code can proceed.
|
|
84
|
+
|
|
85
|
+
## Verdict
|
|
86
|
+
|
|
87
|
+
After completing your review, write your verdict to `/tmp/jtbd-verdict`:
|
|
88
|
+
- `printf 'PASS' > /tmp/jtbd-verdict` — change aligns with documented jobs and persona
|
|
89
|
+
- `printf 'FAIL' > /tmp/jtbd-verdict` — misalignment, job gap, or persona gap detected
|
|
90
|
+
|
|
91
|
+
## Constraints
|
|
92
|
+
|
|
93
|
+
- You are read-only. You do not edit files (except writing the verdict file).
|
|
94
|
+
- You review user-facing UI files.
|
|
95
|
+
- If the change is purely structural with no user-visible impact (CSS refactor, types, imports), report PASS.
|
|
96
|
+
- Do not review accessibility (that is accessibility-lead's job).
|
|
97
|
+
- Do not review styling (that is style-guide-lead's job).
|
|
98
|
+
- Do not review copy/tone (that is voice-and-tone-lead's job).
|
|
99
|
+
- Focus on: does this change serve a real user job, and does it fit the persona?
|
package/bin/install.mjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { resolve, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const utils = await import(resolve(__dirname, "../lib/install-utils.mjs"));
|
|
8
|
+
|
|
9
|
+
const PLUGIN = "wr-jtbd";
|
|
10
|
+
const DEPS = [];
|
|
11
|
+
|
|
12
|
+
const flags = utils.parseStandardArgs(process.argv);
|
|
13
|
+
|
|
14
|
+
if (flags.help) {
|
|
15
|
+
console.log(`
|
|
16
|
+
Usage: npx @windyroad/jtbd [options]
|
|
17
|
+
|
|
18
|
+
Jobs-to-be-done enforcement for UI changes
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--update Update this plugin and its skills
|
|
22
|
+
--uninstall Remove this plugin
|
|
23
|
+
--dry-run Show what would be done without executing
|
|
24
|
+
--help, -h Show this help
|
|
25
|
+
`);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (flags.dryRun) {
|
|
30
|
+
utils.setDryRun(true);
|
|
31
|
+
console.log("[dry-run mode — no commands will be executed]\n");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
utils.checkPrerequisites();
|
|
35
|
+
|
|
36
|
+
if (flags.uninstall) {
|
|
37
|
+
utils.uninstallPackage(PLUGIN);
|
|
38
|
+
} else if (flags.update) {
|
|
39
|
+
utils.updatePackage(PLUGIN);
|
|
40
|
+
} else {
|
|
41
|
+
utils.installPackage(PLUGIN, { deps: DEPS });
|
|
42
|
+
}
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"UserPromptSubmit": [
|
|
4
|
+
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-eval.sh" }] }
|
|
5
|
+
],
|
|
6
|
+
"PreToolUse": [
|
|
7
|
+
{ "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-enforce-edit.sh" }] }
|
|
8
|
+
],
|
|
9
|
+
"PostToolUse": [
|
|
10
|
+
{ "matcher": "Agent", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-mark-reviewed.sh" }] }
|
|
11
|
+
],
|
|
12
|
+
"Stop": [
|
|
13
|
+
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-reset-marker.sh" }] }
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# JTBD - PreToolUse enforcement hook
|
|
3
|
+
# BLOCKS Edit/Write to UI files until jtbd-lead is consulted.
|
|
4
|
+
# Uses shared review-gate.sh for TTL, drift detection, and fail-closed.
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
source "$SCRIPT_DIR/lib/review-gate.sh"
|
|
8
|
+
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
|
|
11
|
+
FILE_PATH=$(echo "$INPUT" | python3 -c "
|
|
12
|
+
import sys, json
|
|
13
|
+
try:
|
|
14
|
+
data = json.load(sys.stdin)
|
|
15
|
+
print(data.get('tool_input', {}).get('file_path', ''))
|
|
16
|
+
except:
|
|
17
|
+
print('')
|
|
18
|
+
" 2>/dev/null || echo "")
|
|
19
|
+
|
|
20
|
+
SESSION_ID=$(echo "$INPUT" | python3 -c "
|
|
21
|
+
import sys, json
|
|
22
|
+
try:
|
|
23
|
+
data = json.load(sys.stdin)
|
|
24
|
+
print(data.get('session_id', ''))
|
|
25
|
+
except:
|
|
26
|
+
print('')
|
|
27
|
+
" 2>/dev/null || echo "")
|
|
28
|
+
|
|
29
|
+
if [ -z "$SESSION_ID" ]; then
|
|
30
|
+
review_gate_parse_error
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
if [ -z "$FILE_PATH" ]; then
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Gate UI component and route files
|
|
39
|
+
case "$FILE_PATH" in
|
|
40
|
+
*.html|*.jsx|*.tsx|*.vue|*.svelte|*.ejs|*.hbs) ;;
|
|
41
|
+
*) exit 0 ;;
|
|
42
|
+
esac
|
|
43
|
+
|
|
44
|
+
BASENAME=$(basename "$FILE_PATH")
|
|
45
|
+
|
|
46
|
+
# If no JTBD doc exists, block and direct to create skill
|
|
47
|
+
if [ ! -f "docs/JOBS_TO_BE_DONE.md" ]; then
|
|
48
|
+
review_gate_deny "BLOCKED: Cannot edit '${BASENAME}' because docs/JOBS_TO_BE_DONE.md does not exist. Run /wr-jtbd:create to generate a JTBD document for this project, then delegate to wr-jtbd:agent for review."
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Check gate with TTL + drift detection
|
|
53
|
+
if check_review_gate "$SESSION_ID" "jtbd" "docs/JOBS_TO_BE_DONE.md"; then
|
|
54
|
+
exit 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
review_gate_deny "BLOCKED: Cannot edit '${BASENAME}' without JTBD review. You MUST first delegate to wr-jtbd:agent using the Agent tool (subagent_type: 'wr-jtbd:agent'). ${REVIEW_GATE_REASON}"
|
|
58
|
+
exit 0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# JTBD - UserPromptSubmit hook
|
|
3
|
+
# Detects JOBS_TO_BE_DONE.md in the project and injects delegation instruction.
|
|
4
|
+
# If the file doesn't exist, instructs Claude to create it via the agent.
|
|
5
|
+
|
|
6
|
+
if [ -f "docs/JOBS_TO_BE_DONE.md" ]; then
|
|
7
|
+
cat <<'HOOK_OUTPUT'
|
|
8
|
+
INSTRUCTION: MANDATORY JTBD CHECK. YOU MUST FOLLOW THIS.
|
|
9
|
+
DETECTED: docs/JOBS_TO_BE_DONE.md exists in this project.
|
|
10
|
+
|
|
11
|
+
This is a NON-OPTIONAL instruction. You MUST use the jtbd-lead agent
|
|
12
|
+
before editing any user-facing UI file (.html, .jsx, .tsx, .vue, .svelte, .ejs, .hbs).
|
|
13
|
+
This is proactive. Do not wait for the user to ask.
|
|
14
|
+
|
|
15
|
+
REQUIRED ACTIONS:
|
|
16
|
+
1. Use the Agent tool to delegate to wr-jtbd:agent
|
|
17
|
+
(subagent_type: "wr-jtbd:agent")
|
|
18
|
+
2. The jtbd-lead will review proposed changes against docs/JOBS_TO_BE_DONE.md
|
|
19
|
+
and PRODUCT_DISCOVERY.md persona definitions
|
|
20
|
+
3. Do NOT write or edit UI code without jtbd-lead review FIRST
|
|
21
|
+
4. Do NOT skip this step even if you think you can handle it yourself
|
|
22
|
+
|
|
23
|
+
SCOPE: User-facing files (.html, .jsx, .tsx, .vue, .svelte, .ejs, .hbs).
|
|
24
|
+
Does NOT apply to: .css files, .ts/.js backend files, config files.
|
|
25
|
+
HOOK_OUTPUT
|
|
26
|
+
else
|
|
27
|
+
# Check if this is a web project (has UI files)
|
|
28
|
+
if ls src/**/*.tsx src/**/*.jsx src/**/*.html 2>/dev/null | head -1 | grep -q .; then
|
|
29
|
+
cat <<'HOOK_OUTPUT'
|
|
30
|
+
NOTE: This project has UI files but no docs/JOBS_TO_BE_DONE.md.
|
|
31
|
+
If the user's task involves editing UI files, the edit will be blocked
|
|
32
|
+
until a JTBD document exists. Run /wr-jtbd:create to generate one.
|
|
33
|
+
HOOK_OUTPUT
|
|
34
|
+
fi
|
|
35
|
+
fi
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# JTBD - PostToolUse hook for Agent tool
|
|
3
|
+
# Creates session markers when jtbd-lead has been consulted with PASS verdict.
|
|
4
|
+
# Handles both edit review and plan review verdicts.
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
source "$SCRIPT_DIR/lib/review-gate.sh"
|
|
8
|
+
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
|
|
11
|
+
SUBAGENT=$(echo "$INPUT" | jq -r '.tool_input.subagent_type // empty') || true
|
|
12
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') || true
|
|
13
|
+
|
|
14
|
+
if [ -z "$SESSION_ID" ]; then
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
case "$SUBAGENT" in
|
|
19
|
+
*jtbd-lead*|*wr-jtbd*)
|
|
20
|
+
# Check for edit review verdict
|
|
21
|
+
VERDICT_FILE="/tmp/jtbd-verdict"
|
|
22
|
+
VERDICT=""
|
|
23
|
+
if [ -f "$VERDICT_FILE" ]; then
|
|
24
|
+
VERDICT=$(cat "$VERDICT_FILE")
|
|
25
|
+
rm -f "$VERDICT_FILE"
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
case "$VERDICT" in
|
|
29
|
+
PASS)
|
|
30
|
+
touch "/tmp/jtbd-reviewed-${SESSION_ID}"
|
|
31
|
+
store_review_hash "$SESSION_ID" "jtbd" "docs/jtbd"
|
|
32
|
+
;;
|
|
33
|
+
FAIL)
|
|
34
|
+
# Do NOT create marker — review found issues
|
|
35
|
+
;;
|
|
36
|
+
*)
|
|
37
|
+
# No verdict file — backward compat, allow with marker
|
|
38
|
+
touch "/tmp/jtbd-reviewed-${SESSION_ID}"
|
|
39
|
+
store_review_hash "$SESSION_ID" "jtbd" "docs/jtbd"
|
|
40
|
+
;;
|
|
41
|
+
esac
|
|
42
|
+
|
|
43
|
+
# Plan review: agent completion = reviewed.
|
|
44
|
+
# The main agent must actually run the review agent to reach this hook.
|
|
45
|
+
# No verdict file needed — PostToolUse:Agent is the unforgeable signal.
|
|
46
|
+
touch "/tmp/jtbd-plan-reviewed-${SESSION_ID}"
|
|
47
|
+
;;
|
|
48
|
+
esac
|
|
49
|
+
|
|
50
|
+
exit 0
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Stop hook: Clears JTBD review session markers.
|
|
3
|
+
|
|
4
|
+
INPUT=$(cat)
|
|
5
|
+
|
|
6
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') || true
|
|
7
|
+
|
|
8
|
+
if [ -n "$SESSION_ID" ]; then
|
|
9
|
+
rm -f "/tmp/jtbd-reviewed-${SESSION_ID}" \
|
|
10
|
+
"/tmp/jtbd-reviewed-${SESSION_ID}.hash" \
|
|
11
|
+
"/tmp/jtbd-verdict" \
|
|
12
|
+
"/tmp/jtbd-plan-reviewed-${SESSION_ID}" \
|
|
13
|
+
"/tmp/jtbd-plan-verdict"
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
exit 0
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Shared gate logic for review enforcement hooks (a11y, voice-tone, style-guide).
|
|
3
|
+
# Sourced by *-enforce-edit.sh hooks and review-plan-enforce.sh.
|
|
4
|
+
# Provides: check_review_gate, review_gate_deny, review_gate_parse_error
|
|
5
|
+
|
|
6
|
+
# Source shared portable helpers (_mtime, _hashcmd)
|
|
7
|
+
_REVIEW_GATE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source "$_REVIEW_GATE_DIR/gate-helpers.sh"
|
|
9
|
+
|
|
10
|
+
# Check review gate marker. Returns 0 if marker is valid (allow), 1 if invalid (deny).
|
|
11
|
+
# Sets REVIEW_GATE_REASON on failure.
|
|
12
|
+
# Usage: check_review_gate "$SESSION_ID" "style-guide" "docs/STYLE-GUIDE.md"
|
|
13
|
+
check_review_gate() {
|
|
14
|
+
local SESSION_ID="$1"
|
|
15
|
+
local SYSTEM="$2" # e.g., "a11y", "voice-tone", "style-guide"
|
|
16
|
+
local POLICY_FILE="$3" # e.g., "docs/STYLE-GUIDE.md"
|
|
17
|
+
local MARKER="/tmp/${SYSTEM}-reviewed-${SESSION_ID}"
|
|
18
|
+
local HASH_FILE="/tmp/${SYSTEM}-reviewed-${SESSION_ID}.hash"
|
|
19
|
+
local TTL_SECONDS="${REVIEW_TTL:-600}"
|
|
20
|
+
|
|
21
|
+
# 1. Marker must exist
|
|
22
|
+
if [ ! -f "$MARKER" ]; then
|
|
23
|
+
REVIEW_GATE_REASON="No ${SYSTEM} review marker found. The ${SYSTEM} agent must review first."
|
|
24
|
+
return 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# 2. TTL check — marker mtime must be within TTL
|
|
28
|
+
local NOW=$(date +%s)
|
|
29
|
+
local MARKER_TIME=$(_mtime "$MARKER")
|
|
30
|
+
local AGE=$(( NOW - MARKER_TIME ))
|
|
31
|
+
if [ "$AGE" -ge "$TTL_SECONDS" ]; then
|
|
32
|
+
rm -f "$MARKER" "$HASH_FILE"
|
|
33
|
+
REVIEW_GATE_REASON="${SYSTEM} review expired (${AGE}s old, TTL ${TTL_SECONDS}s). Re-run the ${SYSTEM} agent."
|
|
34
|
+
return 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# 3. Drift detection — policy file hash must match
|
|
38
|
+
if [ -f "$HASH_FILE" ] && [ -n "$POLICY_FILE" ]; then
|
|
39
|
+
local STORED_HASH=$(cat "$HASH_FILE")
|
|
40
|
+
local CURRENT_HASH=""
|
|
41
|
+
if [ -f "$POLICY_FILE" ]; then
|
|
42
|
+
CURRENT_HASH=$(cat "$POLICY_FILE" | _hashcmd | cut -d' ' -f1)
|
|
43
|
+
elif [ -d "$POLICY_FILE" ]; then
|
|
44
|
+
# Directory (e.g., docs/decisions/) — hash all .md files
|
|
45
|
+
CURRENT_HASH=$(find "$POLICY_FILE" -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
|
|
46
|
+
else
|
|
47
|
+
CURRENT_HASH="missing"
|
|
48
|
+
fi
|
|
49
|
+
if [ "$STORED_HASH" != "$CURRENT_HASH" ]; then
|
|
50
|
+
rm -f "$MARKER" "$HASH_FILE"
|
|
51
|
+
REVIEW_GATE_REASON="${SYSTEM} policy file changed since last review. Re-run the ${SYSTEM} agent."
|
|
52
|
+
return 1
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Slide TTL window forward
|
|
57
|
+
touch "$MARKER"
|
|
58
|
+
return 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Store policy file hash after a successful review.
|
|
62
|
+
# Usage: store_review_hash "$SESSION_ID" "style-guide" "docs/STYLE-GUIDE.md"
|
|
63
|
+
store_review_hash() {
|
|
64
|
+
local SESSION_ID="$1"
|
|
65
|
+
local SYSTEM="$2"
|
|
66
|
+
local POLICY_FILE="$3"
|
|
67
|
+
local HASH_FILE="/tmp/${SYSTEM}-reviewed-${SESSION_ID}.hash"
|
|
68
|
+
|
|
69
|
+
if [ -n "$POLICY_FILE" ]; then
|
|
70
|
+
local HASH=""
|
|
71
|
+
if [ -f "$POLICY_FILE" ]; then
|
|
72
|
+
HASH=$(cat "$POLICY_FILE" | _hashcmd | cut -d' ' -f1)
|
|
73
|
+
elif [ -d "$POLICY_FILE" ]; then
|
|
74
|
+
HASH=$(find "$POLICY_FILE" -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
|
|
75
|
+
else
|
|
76
|
+
HASH="missing"
|
|
77
|
+
fi
|
|
78
|
+
echo "$HASH" > "$HASH_FILE"
|
|
79
|
+
fi
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Emit fail-closed deny JSON for PreToolUse hooks.
|
|
83
|
+
review_gate_deny() {
|
|
84
|
+
local REASON="$1"
|
|
85
|
+
cat <<EOF
|
|
86
|
+
{
|
|
87
|
+
"hookSpecificOutput": {
|
|
88
|
+
"hookEventName": "PreToolUse",
|
|
89
|
+
"permissionDecision": "deny",
|
|
90
|
+
"permissionDecisionReason": "$REASON"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
EOF
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Emit fail-closed deny JSON for parse failures.
|
|
97
|
+
review_gate_parse_error() {
|
|
98
|
+
cat <<'EOF'
|
|
99
|
+
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny",
|
|
100
|
+
"permissionDecisionReason": "BLOCKED: Could not parse hook input. Gate is fail-closed." } }
|
|
101
|
+
EOF
|
|
102
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared install utilities for @windyroad/* packages.
|
|
3
|
+
* Used by both per-plugin installers and the meta-installer.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
const MARKETPLACE_REPO = "windyroad/agent-plugins";
|
|
9
|
+
const MARKETPLACE_NAME = "windyroad";
|
|
10
|
+
|
|
11
|
+
let _dryRun = false;
|
|
12
|
+
|
|
13
|
+
export { MARKETPLACE_REPO, MARKETPLACE_NAME };
|
|
14
|
+
|
|
15
|
+
export function setDryRun(value) {
|
|
16
|
+
_dryRun = value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isDryRun() {
|
|
20
|
+
return _dryRun;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function run(cmd, label) {
|
|
24
|
+
console.log(` ${label}...`);
|
|
25
|
+
if (_dryRun) {
|
|
26
|
+
console.log(` [dry-run] ${cmd}`);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
execSync(cmd, { stdio: "inherit" });
|
|
31
|
+
return true;
|
|
32
|
+
} catch {
|
|
33
|
+
console.error(` FAILED: ${label}`);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function checkPrerequisites() {
|
|
39
|
+
if (_dryRun) return;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
execSync("claude --version", { stdio: "pipe" });
|
|
43
|
+
} catch {
|
|
44
|
+
console.error(
|
|
45
|
+
"Error: 'claude' CLI not found. Install Claude Code first:\n https://docs.anthropic.com/en/docs/claude-code\n"
|
|
46
|
+
);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
execSync("npx --version", { stdio: "pipe" });
|
|
52
|
+
} catch {
|
|
53
|
+
console.error(
|
|
54
|
+
"Error: 'npx' not found. Install Node.js first:\n https://nodejs.org\n"
|
|
55
|
+
);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function addMarketplace() {
|
|
61
|
+
return run(
|
|
62
|
+
`claude plugin marketplace add ${MARKETPLACE_REPO}`,
|
|
63
|
+
`Marketplace: ${MARKETPLACE_NAME}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function installPlugin(pluginName) {
|
|
68
|
+
return run(
|
|
69
|
+
`claude plugin install ${pluginName}@${MARKETPLACE_NAME}`,
|
|
70
|
+
pluginName
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function updatePlugin(pluginName) {
|
|
75
|
+
return run(`claude plugin update ${pluginName}`, pluginName);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function uninstallPlugin(pluginName) {
|
|
79
|
+
return run(`claude plugin uninstall ${pluginName}`, `Removing ${pluginName}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function installSkills() {
|
|
83
|
+
return run(
|
|
84
|
+
`npx -y skills add --yes --all ${MARKETPLACE_REPO}`,
|
|
85
|
+
"Skills (via skills package)"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function updateSkills() {
|
|
90
|
+
return run("npx -y skills update", "Skills update");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function removeSkills() {
|
|
94
|
+
return run(
|
|
95
|
+
`npx -y skills remove --yes --all ${MARKETPLACE_REPO}`,
|
|
96
|
+
"Removing skills"
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Install a single package: marketplace add + plugin install + skills.
|
|
102
|
+
*/
|
|
103
|
+
export function installPackage(pluginName, { deps = [] } = {}) {
|
|
104
|
+
console.log(`\nInstalling @windyroad/${pluginName.replace("wr-", "")}...\n`);
|
|
105
|
+
|
|
106
|
+
addMarketplace();
|
|
107
|
+
installPlugin(pluginName);
|
|
108
|
+
installSkills();
|
|
109
|
+
|
|
110
|
+
if (deps.length > 0) {
|
|
111
|
+
console.log(`\nNote: This plugin works best with:`);
|
|
112
|
+
for (const dep of deps) {
|
|
113
|
+
console.log(` - @windyroad/${dep.replace("wr-", "")} (npx @windyroad/${dep.replace("wr-", "")})`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log(
|
|
118
|
+
`\nDone! Restart Claude Code to activate.\n`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Update a single package.
|
|
124
|
+
*/
|
|
125
|
+
export function updatePackage(pluginName) {
|
|
126
|
+
console.log(`\nUpdating @windyroad/${pluginName.replace("wr-", "")}...\n`);
|
|
127
|
+
|
|
128
|
+
run(
|
|
129
|
+
`claude plugin marketplace update ${MARKETPLACE_NAME}`,
|
|
130
|
+
"Updating marketplace"
|
|
131
|
+
);
|
|
132
|
+
updatePlugin(pluginName);
|
|
133
|
+
updateSkills();
|
|
134
|
+
|
|
135
|
+
console.log("\nDone! Restart Claude Code to apply updates.\n");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Uninstall a single package.
|
|
140
|
+
*/
|
|
141
|
+
export function uninstallPackage(pluginName) {
|
|
142
|
+
console.log(`\nUninstalling @windyroad/${pluginName.replace("wr-", "")}...\n`);
|
|
143
|
+
|
|
144
|
+
uninstallPlugin(pluginName);
|
|
145
|
+
|
|
146
|
+
console.log("\nDone. Restart Claude Code to apply changes.\n");
|
|
147
|
+
console.log("Note: Skills are shared across packages. Run");
|
|
148
|
+
console.log(" npx @windyroad/agent-plugins --uninstall");
|
|
149
|
+
console.log("to remove all skills.\n");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Parse standard flags used by all per-plugin installers.
|
|
154
|
+
*/
|
|
155
|
+
export function parseStandardArgs(argv) {
|
|
156
|
+
const args = argv.slice(2);
|
|
157
|
+
return {
|
|
158
|
+
help: args.includes("--help") || args.includes("-h"),
|
|
159
|
+
uninstall: args.includes("--uninstall"),
|
|
160
|
+
update: args.includes("--update"),
|
|
161
|
+
dryRun: args.includes("--dry-run"),
|
|
162
|
+
};
|
|
163
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@windyroad/jtbd",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Jobs-to-be-done enforcement for UI changes",
|
|
5
|
+
"bin": {
|
|
6
|
+
"windyroad-jtbd": "./bin/install.mjs"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/windyroad/agent-plugins.git",
|
|
13
|
+
"directory": "packages/jtbd"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude-code",
|
|
17
|
+
"claude-code-plugin",
|
|
18
|
+
"ai-agent",
|
|
19
|
+
"ai-coding"
|
|
20
|
+
],
|
|
21
|
+
"files": [
|
|
22
|
+
"bin/",
|
|
23
|
+
"agents/",
|
|
24
|
+
"hooks/",
|
|
25
|
+
"skills/",
|
|
26
|
+
".claude-plugin/",
|
|
27
|
+
"lib/"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wr:jtbd
|
|
3
|
+
description: Create or update the project's docs/JOBS_TO_BE_DONE.md by examining existing features and asking the user about user jobs, personas, and desired outcomes.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Jobs To Be Done Document Generator
|
|
8
|
+
|
|
9
|
+
Create or update `docs/JOBS_TO_BE_DONE.md` tailored to this project's users and their goals. The jtbd-lead agent reads this file to review UI changes against user jobs.
|
|
10
|
+
|
|
11
|
+
## What belongs in docs/JOBS_TO_BE_DONE.md
|
|
12
|
+
|
|
13
|
+
- **User personas**: Who uses this product and what characterises them
|
|
14
|
+
- **Jobs**: What users are trying to accomplish (functional, emotional, social)
|
|
15
|
+
- **Job stories**: "When [situation], I want to [motivation], so I can [expected outcome]"
|
|
16
|
+
- **Desired outcomes**: Measurable results users want from each job
|
|
17
|
+
- **Current solutions**: How users currently accomplish these jobs (competitors, workarounds)
|
|
18
|
+
- **Last reviewed date**: When the document was last reviewed or updated
|
|
19
|
+
|
|
20
|
+
## Steps
|
|
21
|
+
|
|
22
|
+
### 1. Discover project context
|
|
23
|
+
|
|
24
|
+
Examine the project to understand what it does and who uses it.
|
|
25
|
+
|
|
26
|
+
**Find the product definition** by scanning for:
|
|
27
|
+
- README.md and documentation
|
|
28
|
+
- Landing page or marketing content
|
|
29
|
+
- Product discovery documents (PRODUCT_DISCOVERY.md, personas, user research)
|
|
30
|
+
- Route/page structure (reveals user workflows)
|
|
31
|
+
- Feature flags or configuration (reveals capabilities)
|
|
32
|
+
|
|
33
|
+
**Discover user workflows**:
|
|
34
|
+
- Map the main user-facing pages/screens and their purpose
|
|
35
|
+
- Identify the core user journey (what do users do from start to finish?)
|
|
36
|
+
- Look for onboarding flows, dashboards, settings, or admin areas
|
|
37
|
+
- Check for different user roles (admin, member, viewer, etc.)
|
|
38
|
+
|
|
39
|
+
**Discover existing JTBD artefacts**:
|
|
40
|
+
- User stories in issues or project boards
|
|
41
|
+
- Persona documents
|
|
42
|
+
- User research notes or interview transcripts
|
|
43
|
+
- Analytics configuration (what events are tracked?)
|
|
44
|
+
|
|
45
|
+
### 2. Check for existing document
|
|
46
|
+
|
|
47
|
+
If `docs/JOBS_TO_BE_DONE.md` already exists, read it. Identify:
|
|
48
|
+
- Whether jobs still match the current feature set
|
|
49
|
+
- Whether personas still reflect the actual user base
|
|
50
|
+
- Whether the last reviewed date is stale (> 2 weeks)
|
|
51
|
+
|
|
52
|
+
### 3. Draft the JTBD document
|
|
53
|
+
|
|
54
|
+
Based on project discovery, draft sections covering:
|
|
55
|
+
|
|
56
|
+
**Personas** (2-4):
|
|
57
|
+
For each persona, describe: who they are, what characterises them, what they care about, and what frustrates them. Ground these in the actual product, not generic archetypes.
|
|
58
|
+
|
|
59
|
+
**Jobs** (3-8):
|
|
60
|
+
For each job:
|
|
61
|
+
- A job statement: "Help [persona] [accomplish goal] when [situation]"
|
|
62
|
+
- Whether it's functional, emotional, or social
|
|
63
|
+
- Priority (must-have, important, nice-to-have)
|
|
64
|
+
|
|
65
|
+
**Job Stories** (1-2 per job):
|
|
66
|
+
"When [situation], I want to [motivation], so I can [expected outcome]"
|
|
67
|
+
|
|
68
|
+
**Desired Outcomes** (per job):
|
|
69
|
+
What does success look like? How would the user measure it?
|
|
70
|
+
|
|
71
|
+
**Current Solutions**:
|
|
72
|
+
How do users currently accomplish these jobs without (or with competitors to) this product?
|
|
73
|
+
|
|
74
|
+
### 4. Confirm with the user
|
|
75
|
+
|
|
76
|
+
You MUST use the AskUserQuestion tool to collect user confirmation.
|
|
77
|
+
|
|
78
|
+
Present:
|
|
79
|
+
1. The drafted personas and ask if they're accurate
|
|
80
|
+
2. The jobs identified and ask if they cover the core value proposition
|
|
81
|
+
3. The job stories and ask if the situations and motivations ring true
|
|
82
|
+
4. Whether any user segments or jobs are missing
|
|
83
|
+
|
|
84
|
+
### 5. Write docs/JOBS_TO_BE_DONE.md
|
|
85
|
+
|
|
86
|
+
Write the document including:
|
|
87
|
+
- A header with "Last reviewed" date (today's date)
|
|
88
|
+
- All sections from step 3, refined based on user feedback from step 4
|
|
89
|
+
- A note that the wr-jtbd:agent reads this file to review UI changes against user jobs
|
|
90
|
+
|
|
91
|
+
If updating rather than creating:
|
|
92
|
+
- Preserve existing content the user hasn't asked to change
|
|
93
|
+
- Show the user a diff of what changed
|
|
94
|
+
- Update the "Last reviewed" date
|
|
95
|
+
|
|
96
|
+
$ARGUMENTS
|