@windyroad/voice-tone 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.
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "wr-voice-tone",
3
+ "version": "0.1.0",
4
+ "description": "Voice and tone enforcement for Claude Code"
5
+ }
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: agent
3
+ description: Voice and tone reviewer for copy changes. Use before editing any
4
+ user-facing copy in source files. Reads docs/VOICE-AND-TONE.md and reviews
5
+ proposed changes against the guide's voice principles, tone guidance, banned
6
+ patterns, and word list. Reports violations with suggested fixes.
7
+ tools:
8
+ - Read
9
+ - Glob
10
+ - Grep
11
+ model: inherit
12
+ ---
13
+
14
+ You are the Voice and Tone Lead. You review proposed copy changes against the project's docs/VOICE-AND-TONE.md guide before any user-facing text is edited. You are a reviewer, not an editor.
15
+
16
+ ## Your Role
17
+
18
+ 1. Read `docs/VOICE-AND-TONE.md` in the project to load the current guide
19
+ 2. Read the file(s) being edited to understand the existing copy and context
20
+ 3. Review proposed changes against every section of the guide
21
+ 4. Report: OK if compliant, or list specific violations with suggested fixes
22
+
23
+ ## What You Check
24
+
25
+ All review criteria come from `docs/VOICE-AND-TONE.md`. Read the guide first and apply its sections. Typical sections include:
26
+
27
+ - **Voice principles** — the personality and values behind the copy
28
+ - **Tone by context** — how tone varies by situation (errors, onboarding, success, etc.)
29
+ - **Banned patterns** — specific phrases or anti-patterns to reject
30
+ - **Word list / terminology** — preferred terms and domain vocabulary
31
+ - **Language & locale** — spelling conventions, casing rules, app name formatting
32
+
33
+ If the guide defines additional sections, check those too. Do not invent rules that are not in the guide.
34
+
35
+ ## How to Report
36
+
37
+ If the copy is compliant:
38
+ > **Voice & Tone Review: PASS**
39
+ > No violations found. Copy aligns with the voice guide.
40
+
41
+ If there are violations, list each one:
42
+
43
+ > **Voice & Tone Review: VIOLATIONS FOUND**
44
+ >
45
+ > 1. **[Principle/Rule]** - File: `path`, Line ~N
46
+ > - **Issue**: What is wrong
47
+ > - **Copy**: The offending text
48
+ > - **Fix**: Suggested replacement
49
+ >
50
+ > 2. ...
51
+
52
+ ## Guide Gap Detection
53
+
54
+ If the code introduces a UI context, audience, or copy pattern not covered by `docs/VOICE-AND-TONE.md`, flag this as a guide gap:
55
+
56
+ > **Voice & Tone Review: GUIDE UPDATE NEEDED**
57
+ >
58
+ > The code introduces [context/audience/pattern] which is not covered by the current voice and tone guide.
59
+ > Recommended addition to `docs/VOICE-AND-TONE.md`: [specific section/content to add]
60
+
61
+ This is a FAIL verdict — the guide must be updated before the code can proceed. Write `printf 'FAIL' > /tmp/voice-tone-verdict` for guide gaps.
62
+
63
+ ## Verdict
64
+
65
+ After completing your review, write your verdict to `/tmp/voice-tone-verdict`:
66
+ - `printf 'PASS' > /tmp/voice-tone-verdict` — copy is compliant and guide covers the context
67
+ - `printf 'FAIL' > /tmp/voice-tone-verdict` — violations found or guide gap detected
68
+
69
+ ## Constraints
70
+
71
+ - You are read-only. You do not edit files (except writing the verdict file).
72
+ - You review copy in user-facing source files.
73
+ - If the change is purely structural (no user-visible text changes), report PASS.
74
+ - Do not block styling-only changes (CSS classes, layout, imports with no copy).
@@ -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-voice-tone";
10
+ const DEPS = [];
11
+
12
+ const flags = utils.parseStandardArgs(process.argv);
13
+
14
+ if (flags.help) {
15
+ console.log(`
16
+ Usage: npx @windyroad/voice-tone [options]
17
+
18
+ Voice and tone enforcement for user-facing copy
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
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "hooks": {
3
+ "UserPromptSubmit": [
4
+ { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/voice-tone-eval.sh" }] }
5
+ ],
6
+ "PreToolUse": [
7
+ { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/voice-tone-enforce-edit.sh" }] }
8
+ ],
9
+ "PostToolUse": [
10
+ { "matcher": "Agent", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/voice-tone-mark-reviewed.sh" }] }
11
+ ],
12
+ "Stop": [
13
+ { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/voice-tone-reset-marker.sh" }] }
14
+ ]
15
+ }
16
+ }
@@ -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,58 @@
1
+ #!/bin/bash
2
+ # Voice & Tone - PreToolUse enforcement hook
3
+ # BLOCKS Edit/Write to copy-bearing files until voice-and-tone-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 copy-bearing 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 policy file exists, block and direct to create skill
47
+ if [ ! -f "docs/VOICE-AND-TONE.md" ]; then
48
+ review_gate_deny "BLOCKED: Cannot edit '${BASENAME}' because docs/VOICE-AND-TONE.md does not exist. Run /wr-voice-tone:create to generate a voice and tone guide for this project, then delegate to wr-voice-tone:agent for review."
49
+ exit 0
50
+ fi
51
+
52
+ # Check gate with TTL + drift detection
53
+ if check_review_gate "$SESSION_ID" "voice-tone" "docs/VOICE-AND-TONE.md"; then
54
+ exit 0
55
+ fi
56
+
57
+ review_gate_deny "BLOCKED: Cannot edit '${BASENAME}' without voice & tone review. You MUST first delegate to wr-voice-tone:agent using the Agent tool (subagent_type: 'wr-voice-tone:agent'). ${REVIEW_GATE_REASON}"
58
+ exit 0
@@ -0,0 +1,34 @@
1
+ #!/bin/bash
2
+ # Voice & Tone - UserPromptSubmit hook
3
+ # Detects VOICE-AND-TONE.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/VOICE-AND-TONE.md" ]; then
7
+ cat <<'HOOK_OUTPUT'
8
+ INSTRUCTION: MANDATORY VOICE & TONE CHECK. YOU MUST FOLLOW THIS.
9
+ DETECTED: docs/VOICE-AND-TONE.md exists in this project.
10
+
11
+ This is a NON-OPTIONAL instruction. You MUST use the voice-and-tone-lead agent
12
+ before editing any user-facing copy in HTML, JSX, template, or component files.
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-voice-tone:agent
17
+ (subagent_type: "wr-voice-tone:agent")
18
+ 2. The voice-and-tone-lead will review proposed copy against docs/VOICE-AND-TONE.md
19
+ 3. Do NOT write or edit copy without voice-and-tone-lead review FIRST
20
+ 4. Do NOT skip this step even if you think you can handle it yourself
21
+
22
+ SCOPE: User-facing files (.html, .jsx, .tsx, .vue, .svelte, .ejs, .hbs).
23
+ Does NOT apply to: .css files, .ts/.js backend files, config files.
24
+ HOOK_OUTPUT
25
+ else
26
+ # Check if this is a web project (has UI files)
27
+ if ls src/**/*.tsx src/**/*.jsx src/**/*.html 2>/dev/null | head -1 | grep -q .; then
28
+ cat <<'HOOK_OUTPUT'
29
+ NOTE: This project has UI files but no docs/VOICE-AND-TONE.md.
30
+ If the user's task involves editing user-facing copy, the edit will be blocked
31
+ until a voice and tone guide exists. Run /wr-voice-tone:create to generate one.
32
+ HOOK_OUTPUT
33
+ fi
34
+ fi
@@ -0,0 +1,49 @@
1
+ #!/bin/bash
2
+ # Voice & Tone - PostToolUse hook for Agent tool
3
+ # Creates a session marker when voice-and-tone-lead has been consulted with PASS verdict.
4
+ # This marker unlocks the voice-tone-enforce-edit.sh PreToolUse block.
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
+ *voice-and-tone-lead*|*wr-voice-tone*)
20
+ VERDICT_FILE="/tmp/voice-tone-verdict"
21
+ VERDICT=""
22
+ if [ -f "$VERDICT_FILE" ]; then
23
+ VERDICT=$(cat "$VERDICT_FILE")
24
+ rm -f "$VERDICT_FILE"
25
+ fi
26
+
27
+ case "$VERDICT" in
28
+ PASS)
29
+ touch "/tmp/voice-tone-reviewed-${SESSION_ID}"
30
+ store_review_hash "$SESSION_ID" "voice-tone" "docs/VOICE-AND-TONE.md"
31
+ ;;
32
+ FAIL)
33
+ # Do NOT create marker — review found issues
34
+ ;;
35
+ *)
36
+ # No verdict file — backward compat, allow with marker
37
+ touch "/tmp/voice-tone-reviewed-${SESSION_ID}"
38
+ store_review_hash "$SESSION_ID" "voice-tone" "docs/VOICE-AND-TONE.md"
39
+ ;;
40
+ esac
41
+
42
+ # Plan review: agent completion = reviewed.
43
+ # The main agent must actually run the review agent to reach this hook.
44
+ # No verdict file needed — PostToolUse:Agent is the unforgeable signal.
45
+ touch "/tmp/voice-tone-plan-reviewed-${SESSION_ID}"
46
+ ;;
47
+ esac
48
+
49
+ exit 0
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+ # Voice & Tone - Stop hook
3
+ # Removes the session marker so the next prompt requires a fresh voice review.
4
+ # This tightens the gate from per-session to per-turn.
5
+
6
+ INPUT=$(cat)
7
+
8
+ SESSION_ID=$(echo "$INPUT" | python3 -c "
9
+ import sys, json
10
+ data = json.load(sys.stdin)
11
+ print(data.get('session_id', ''))
12
+ " 2>/dev/null)
13
+
14
+ if [ -n "$SESSION_ID" ]; then
15
+ rm -f "/tmp/voice-tone-reviewed-${SESSION_ID}" \
16
+ "/tmp/voice-tone-reviewed-${SESSION_ID}.hash" \
17
+ "/tmp/voice-tone-verdict" \
18
+ "/tmp/voice-tone-plan-reviewed-${SESSION_ID}" \
19
+ "/tmp/voice-tone-plan-verdict"
20
+ fi
21
+
22
+ exit 0
@@ -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/voice-tone",
3
+ "version": "0.1.1",
4
+ "description": "Voice and tone enforcement for user-facing copy",
5
+ "bin": {
6
+ "windyroad-voice-tone": "./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/voice-tone"
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,97 @@
1
+ ---
2
+ name: wr:voice-tone
3
+ description: Create or update the project's docs/VOICE-AND-TONE.md by examining existing content and asking the user about brand voice, audience, and tone preferences.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
5
+ ---
6
+
7
+ # Voice and Tone Guide Generator
8
+
9
+ Create or update `docs/VOICE-AND-TONE.md` tailored to this project's brand, audience, and content. The voice-and-tone-lead agent reads this file to review user-facing copy.
10
+
11
+ ## What belongs in docs/VOICE-AND-TONE.md
12
+
13
+ - **Brand voice**: The personality and character of the product's communication
14
+ - **Audience**: Who the product speaks to and their expectations
15
+ - **Tone spectrum**: How tone shifts across contexts (error messages vs success vs onboarding)
16
+ - **Do/Don't examples**: Concrete before/after examples drawn from the actual project
17
+ - **Terminology**: Preferred terms and terms to avoid
18
+ - **Last reviewed date**: When the guide was last reviewed or updated
19
+
20
+ ## Steps
21
+
22
+ ### 1. Discover project context
23
+
24
+ Examine the project to understand what it does, who uses it, and what voice it currently has.
25
+
26
+ **Find existing content** by scanning for:
27
+ - User-facing copy in UI files (.tsx, .jsx, .html, .vue, .svelte, .ejs, .hbs)
28
+ - README.md and documentation
29
+ - Error messages and validation text
30
+ - Marketing or landing page content
31
+ - Existing brand guidelines or tone documentation
32
+
33
+ **Identify the audience**:
34
+ - Is this a developer tool, consumer product, enterprise SaaS, documentation site?
35
+ - Who are the primary users? Technical or non-technical?
36
+ - What is the relationship between the product and its users (formal, casual, peer)?
37
+
38
+ **Sample existing voice**:
39
+ - Read 5-10 representative UI strings or content blocks
40
+ - Note patterns: formal vs casual, technical vs plain, terse vs verbose
41
+ - Identify inconsistencies that the guide should resolve
42
+
43
+ ### 2. Check for existing guide
44
+
45
+ If `docs/VOICE-AND-TONE.md` already exists, read it. Identify:
46
+ - Whether the voice description still matches the current product direction
47
+ - Whether examples reference features or copy that no longer exists
48
+ - Whether the last reviewed date is stale (> 2 weeks)
49
+
50
+ ### 3. Draft the voice and tone guide
51
+
52
+ Based on project discovery, draft sections covering:
53
+
54
+ **Brand Voice** (3-5 adjectives with explanations):
55
+ Example: "Confident but not arrogant. We know our product well but respect the user's expertise."
56
+
57
+ **Audience**:
58
+ Who we're speaking to and what they care about.
59
+
60
+ **Tone Spectrum**:
61
+ How tone adapts to context. Include at least:
62
+ - Success/confirmation messages
63
+ - Error/warning messages
64
+ - Onboarding/help text
65
+ - Empty states
66
+ - Technical documentation
67
+
68
+ **Do/Don't Examples**:
69
+ At least 5 concrete pairs drawn from the actual project's content patterns. Show the wrong way and the right way, with brief explanations.
70
+
71
+ **Terminology**:
72
+ - Preferred terms (and what they replace)
73
+ - Terms to avoid (and why)
74
+
75
+ ### 4. Confirm with the user
76
+
77
+ You MUST use the AskUserQuestion tool to collect user confirmation. Do not proceed to step 5 until you have answers.
78
+
79
+ Present:
80
+ 1. The drafted brand voice adjectives and ask if they match the intended personality
81
+ 2. The audience description and ask if it's accurate
82
+ 3. The do/don't examples and ask if the direction feels right
83
+ 4. Whether any specific tone requirements are missing (e.g., accessibility language, inclusive language, regulatory constraints)
84
+
85
+ ### 5. Write docs/VOICE-AND-TONE.md
86
+
87
+ Write the guide including:
88
+ - A header with "Last reviewed" date (today's date)
89
+ - All sections from step 3, refined based on user feedback from step 4
90
+ - A note that the wr-voice-tone:agent reads this file to review user-facing copy
91
+
92
+ If updating rather than creating:
93
+ - Preserve existing guidance the user hasn't asked to change
94
+ - Show the user a diff of what changed
95
+ - Update the "Last reviewed" date
96
+
97
+ $ARGUMENTS