@windyroad/style-guide 0.5.3 → 0.6.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.
@@ -0,0 +1 @@
1
+ {"name":"windyroad-style-guide-local","interface":{"displayName":"Windy Road Style Guide"},"plugins":[{"name":"wr-style-guide","source":{"source":"local","path":"."},"policy":{"installation":"AVAILABLE","authentication":"ON_INSTALL"},"category":"Developer Tools"}]}
@@ -79,5 +79,5 @@
79
79
  }
80
80
  },
81
81
  "name": "wr-style-guide",
82
- "version": "0.5.3"
82
+ "version": "0.6.0"
83
83
  }
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "wr-style-guide",
3
+ "version": "0.6.0",
4
+ "description": "Style-guide governance for CSS and UI components",
5
+ "author": {
6
+ "name": "Windy Road Technology",
7
+ "url": "https://windyroad.com.au"
8
+ },
9
+ "homepage": "https://github.com/windyroad/agent-plugins/tree/main/packages/style-guide",
10
+ "repository": "https://github.com/windyroad/agent-plugins",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "style-guide",
14
+ "css",
15
+ "design-system",
16
+ "codex",
17
+ "claude-code"
18
+ ],
19
+ "skills": "./skills/",
20
+ "interface": {
21
+ "displayName": "Windy Road Style Guide",
22
+ "shortDescription": "Govern CSS and UI styling changes",
23
+ "longDescription": "Review visual changes against a project style guide and keep the guide aligned with established design patterns.",
24
+ "developerName": "Windy Road Technology",
25
+ "category": "Developer Tools",
26
+ "capabilities": [
27
+ "Interactive",
28
+ "Read",
29
+ "Write"
30
+ ],
31
+ "websiteURL": "https://windyroad.com.au",
32
+ "privacyPolicyURL": "https://windyroad.com.au/privacy",
33
+ "termsOfServiceURL": "https://windyroad.com.au/terms",
34
+ "defaultPrompt": [
35
+ "Review this styling change against the style guide",
36
+ "Update the project style guide"
37
+ ],
38
+ "brandColor": "#DB2777",
39
+ "screenshots": []
40
+ }
41
+ }
package/bin/install.mjs CHANGED
@@ -8,6 +8,7 @@ const utils = await import(resolve(__dirname, "../lib/install-utils.mjs"));
8
8
 
9
9
  const PLUGIN = "wr-style-guide";
10
10
  const DEPS = [];
11
+ const AGENTS = [{ source: "agents/agent.md", name: "wr-style-guide:agent", filename: "wr-style-guide-agent.toml" }];
11
12
 
12
13
  const flags = utils.parseStandardArgs(process.argv);
13
14
 
@@ -21,6 +22,7 @@ Options:
21
22
  --update Update this plugin and its skills
22
23
  --uninstall Remove this plugin
23
24
  --scope Installation scope: project (default) or user
25
+ --runtime Runtime to install for: claude (default), codex, or both
24
26
  --dry-run Show what would be done without executing
25
27
  --help, -h Show this help
26
28
  `);
@@ -32,12 +34,12 @@ if (flags.dryRun) {
32
34
  console.log("[dry-run mode — no commands will be executed]\n");
33
35
  }
34
36
 
35
- utils.checkPrerequisites();
37
+ utils.checkPrerequisites({ runtime: flags.runtime });
36
38
 
37
39
  if (flags.uninstall) {
38
- utils.uninstallPackage(PLUGIN);
40
+ utils.uninstallPackage(PLUGIN, { agents: AGENTS, scope: flags.scope, runtime: flags.runtime });
39
41
  } else if (flags.update) {
40
- utils.updatePackage(PLUGIN, { scope: flags.scope });
42
+ utils.updatePackage(PLUGIN, { agents: AGENTS, scope: flags.scope, runtime: flags.runtime });
41
43
  } else {
42
- utils.installPackage(PLUGIN, { deps: DEPS, scope: flags.scope });
44
+ utils.installPackage(PLUGIN, { agents: AGENTS, deps: DEPS, scope: flags.scope, runtime: flags.runtime });
43
45
  }
package/hooks/hooks.json CHANGED
@@ -1,15 +1,53 @@
1
1
  {
2
2
  "hooks": {
3
3
  "UserPromptSubmit": [
4
- { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/style-guide-eval.sh" }] },
5
- { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/staleness-check.sh" }] }
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/style-guide-eval.sh"
9
+ }
10
+ ]
11
+ },
12
+ {
13
+ "hooks": [
14
+ {
15
+ "type": "command",
16
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/staleness-check.sh"
17
+ }
18
+ ]
19
+ }
6
20
  ],
7
21
  "PreToolUse": [
8
- { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/style-guide-enforce-edit.sh" }] }
22
+ {
23
+ "matcher": "Edit|Write",
24
+ "hooks": [
25
+ {
26
+ "type": "command",
27
+ "command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/style-guide-enforce-edit.sh\""
28
+ }
29
+ ]
30
+ }
9
31
  ],
10
32
  "PostToolUse": [
11
- { "matcher": "Agent", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/style-guide-mark-reviewed.sh" }] },
12
- { "matcher": "Agent|Bash|Skill", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/style-guide-slide-marker.sh" }] }
33
+ {
34
+ "matcher": "Agent",
35
+ "hooks": [
36
+ {
37
+ "type": "command",
38
+ "command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/style-guide-mark-reviewed.sh\""
39
+ }
40
+ ]
41
+ },
42
+ {
43
+ "matcher": "Agent|Bash|Skill",
44
+ "hooks": [
45
+ {
46
+ "type": "command",
47
+ "command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/style-guide-slide-marker.sh\""
48
+ }
49
+ ]
50
+ }
13
51
  ]
14
52
  }
15
53
  }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ set -uo pipefail
3
+
4
+ target="$1"
5
+ input="$(cat)"
6
+ tool="$(printf '%s' "$input" | jq -r '.tool_name // empty')"
7
+
8
+ run_hook() {
9
+ printf '%s' "$1" | "$target"
10
+ }
11
+
12
+ if [ "$tool" = "apply_patch" ]; then
13
+ paths="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' | sed -nE 's/^\*\*\* (Add|Update|Delete) File: (.*)$/\2/p')"
14
+ if [ -n "$paths" ]; then
15
+ while IFS= read -r path; do
16
+ payload="$(printf '%s' "$input" | jq --arg path "$path" '.tool_name = "Edit" | .tool_input.file_path = $path')"
17
+ run_hook "$payload" || exit $?
18
+ done <<< "$paths"
19
+ exit 0
20
+ fi
21
+ fi
22
+
23
+ if [ "$tool" = "spawn_agent" ] || [ "$tool" = "Agent" ]; then
24
+ input="$(printf '%s' "$input" | jq '.tool_name = "Agent" | .tool_input.subagent_type = (.tool_input.agent_type // .tool_input.subagent_type // "") | .tool_input.prompt = (.tool_input.message // .tool_input.prompt // "")')"
25
+ fi
26
+
27
+ run_hook "$input"
@@ -0,0 +1,53 @@
1
+ {
2
+ "hooks": {
3
+ "UserPromptSubmit": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/style-guide-eval.sh"
9
+ }
10
+ ]
11
+ },
12
+ {
13
+ "hooks": [
14
+ {
15
+ "type": "command",
16
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/staleness-check.sh"
17
+ }
18
+ ]
19
+ }
20
+ ],
21
+ "PreToolUse": [
22
+ {
23
+ "matcher": "Edit|Write",
24
+ "hooks": [
25
+ {
26
+ "type": "command",
27
+ "command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/style-guide-enforce-edit.sh\""
28
+ }
29
+ ]
30
+ }
31
+ ],
32
+ "PostToolUse": [
33
+ {
34
+ "matcher": "Agent",
35
+ "hooks": [
36
+ {
37
+ "type": "command",
38
+ "command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/style-guide-mark-reviewed.sh\""
39
+ }
40
+ ]
41
+ },
42
+ {
43
+ "matcher": "Agent|Bash|Skill",
44
+ "hooks": [
45
+ {
46
+ "type": "command",
47
+ "command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/style-guide-slide-marker.sh\""
48
+ }
49
+ ]
50
+ }
51
+ ]
52
+ }
53
+ }
@@ -4,11 +4,17 @@
4
4
  */
5
5
 
6
6
  import { execSync } from "node:child_process";
7
+ import { createHash } from "node:crypto";
8
+ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import { dirname, join, resolve } from "node:path";
11
+ import { fileURLToPath } from "node:url";
7
12
 
8
13
  const MARKETPLACE_REPO = "windyroad/agent-plugins";
9
14
  const MARKETPLACE_NAME = "windyroad";
10
15
  const CODEX_MARKETPLACE_PATH = ".";
11
16
  const CODEX_MARKETPLACE_NAME = "windyroad-local";
17
+ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
12
18
 
13
19
  let _dryRun = false;
14
20
 
@@ -82,6 +88,132 @@ export function addCodexMarketplace() {
82
88
  );
83
89
  }
84
90
 
91
+ function codexMarketplace(pluginName) {
92
+ return `windyroad-${pluginName.replace(/^wr-/, "")}-local`;
93
+ }
94
+
95
+ function codexMarketplaceRoot(pluginName) {
96
+ const version = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8")).version;
97
+ return join(process.env.CODEX_HOME || join(homedir(), ".codex"), ".tmp", "marketplaces", `${pluginName}-${version}`);
98
+ }
99
+
100
+ function codexAgentDir(scope) {
101
+ return scope === "user"
102
+ ? join(process.env.CODEX_HOME || join(homedir(), ".codex"), "agents")
103
+ : join(process.cwd(), ".codex", "agents");
104
+ }
105
+
106
+ function codexTerms(text) {
107
+ return text
108
+ .replaceAll("AskUserQuestion", "request_user_input")
109
+ .replaceAll("Agent tool", "native Codex subagent tool")
110
+ .replaceAll("Skill tool", "installed skill invocation")
111
+ .replaceAll("Claude Code", "Codex")
112
+ .replace(/\bClaude\b/g, "Codex")
113
+ .replaceAll(".claude", ".codex");
114
+ }
115
+
116
+ function splitFrontmatter(markdown) {
117
+ const end = markdown.indexOf("\n---\n", 4);
118
+ return markdown.startsWith("---\n") && end !== -1
119
+ ? { frontmatter: markdown.slice(4, end), body: markdown.slice(end + 5) }
120
+ : { frontmatter: "", body: markdown };
121
+ }
122
+
123
+ function frontmatterDescription(frontmatter) {
124
+ const lines = frontmatter.split(/\r?\n/);
125
+ const start = lines.findIndex((line) => line.startsWith("description:"));
126
+ if (start === -1) return "Windy Road reviewer.";
127
+ const value = [lines[start].slice("description:".length).trim()];
128
+ for (let index = start + 1; index < lines.length && /^\s+/.test(lines[index]); index += 1) {
129
+ value.push(lines[index].trim());
130
+ }
131
+ return value.filter(Boolean).join(" ");
132
+ }
133
+
134
+ function renderCodexAgent(pluginName, agent) {
135
+ const source = join(PACKAGE_ROOT, agent.source);
136
+ const { frontmatter, body } = splitFrontmatter(readFileSync(source, "utf8"));
137
+ const codexInstructions = agent.name === "wr-voice-tone:external-comms"
138
+ ? `\n\n## Codex completion marker compatibility\n\nOn PASS, compute the lowercase SHA-256 marker key using the normalization specified above and append \`EXTERNAL_COMMS_VOICE_TONE_KEY: <64 lowercase hex characters>\`. Codex may hide the spawn prompt from PostToolUse hooks, so this emitted key is required. On FAIL, do not emit a key.`
139
+ : "";
140
+ const payload = [
141
+ `# Do not edit by hand; update ${agent.source} and reinstall.`,
142
+ `name = ${JSON.stringify(agent.name)}`,
143
+ `description = ${JSON.stringify(frontmatterDescription(frontmatter))}`,
144
+ 'sandbox_mode = "read-only"',
145
+ 'developer_instructions = """',
146
+ codexTerms(`${body.trimEnd()}${codexInstructions}`).replace(/\\/g, "\\\\").replace(/"""/g, '\\"\\"\\"').trimEnd(),
147
+ '"""',
148
+ "",
149
+ ].join("\n");
150
+ const owner = `# Generated by @windyroad/${pluginName.replace(/^wr-/, "")} from ${agent.source}.`;
151
+ const hash = createHash("sha256").update(payload).digest("hex");
152
+ return `${owner}\n# Generated content SHA-256: ${hash}\n${payload}`;
153
+ }
154
+
155
+ function isOwnedCodexAgent(content, pluginName, agent) {
156
+ const owner = `# Generated by @windyroad/${pluginName.replace(/^wr-/, "")} from ${agent.source}.`;
157
+ const lines = content.split("\n");
158
+ const hash = lines[1]?.match(/^# Generated content SHA-256: ([0-9a-f]{64})$/)?.[1];
159
+ return content.startsWith(`${owner}\n`) && Boolean(hash)
160
+ && createHash("sha256").update(lines.slice(2).join("\n")).digest("hex") === hash;
161
+ }
162
+
163
+ function installCodexAgents(pluginName, agents, scope) {
164
+ if (agents.length === 0 || _dryRun) return;
165
+ const targetDir = codexAgentDir(scope);
166
+ mkdirSync(targetDir, { recursive: true });
167
+ for (const agent of agents) {
168
+ const target = join(targetDir, agent.filename);
169
+ const expected = renderCodexAgent(pluginName, agent);
170
+ if (existsSync(target)) {
171
+ const current = readFileSync(target, "utf8");
172
+ if (current === expected) continue;
173
+ if (!isOwnedCodexAgent(current, pluginName, agent)) {
174
+ console.log(`Preserved user-managed Codex agent at ${target}.`);
175
+ continue;
176
+ }
177
+ }
178
+ writeFileSync(target, expected, "utf8");
179
+ }
180
+ }
181
+
182
+ function uninstallCodexAgents(pluginName, agents, scope) {
183
+ if (_dryRun) return;
184
+ for (const targetDir of new Set([codexAgentDir(scope), codexAgentDir("user")])) {
185
+ for (const agent of agents) {
186
+ const target = join(targetDir, agent.filename);
187
+ if (existsSync(target) && isOwnedCodexAgent(readFileSync(target, "utf8"), pluginName, agent)) rmSync(target);
188
+ }
189
+ }
190
+ }
191
+
192
+ function installPackedCodexPlugin(pluginName, { agents = [], scope = "project" } = {}) {
193
+ const marketplace = codexMarketplace(pluginName);
194
+ const root = codexMarketplaceRoot(pluginName);
195
+ if (!_dryRun) {
196
+ rmSync(root, { recursive: true, force: true });
197
+ mkdirSync(dirname(root), { recursive: true });
198
+ cpSync(PACKAGE_ROOT, root, { recursive: true });
199
+ const hooks = join(root, "hooks-codex", "hooks.json");
200
+ if (existsSync(hooks)) cpSync(hooks, join(root, "hooks", "hooks.json"));
201
+ }
202
+ if (!run(`codex plugin marketplace add ${JSON.stringify(root)}`, `Codex marketplace: ${marketplace}`)) return false;
203
+ if (!run(`codex plugin add ${pluginName}@${marketplace}`, pluginName)) return false;
204
+ installCodexAgents(pluginName, agents, scope);
205
+ return true;
206
+ }
207
+
208
+ function uninstallPackedCodexPlugin(pluginName, { agents = [], scope = "project" } = {}) {
209
+ const marketplace = codexMarketplace(pluginName);
210
+ const removed = run(`codex plugin remove ${pluginName}@${marketplace}`, `Removing ${pluginName}`);
211
+ run(`codex plugin marketplace remove ${marketplace}`, `Removing ${marketplace}`);
212
+ uninstallCodexAgents(pluginName, agents, scope);
213
+ if (!_dryRun) rmSync(codexMarketplaceRoot(pluginName), { recursive: true, force: true });
214
+ return removed;
215
+ }
216
+
85
217
  export function installPlugin(pluginName, { scope = "project" } = {}) {
86
218
  return run(
87
219
  `claude plugin install ${pluginName}@${MARKETPLACE_NAME} --scope ${scope}`,
@@ -121,7 +253,7 @@ export function uninstallCodexPlugin(pluginName) {
121
253
  /**
122
254
  * Install a single package: marketplace add + plugin install.
123
255
  */
124
- export function installPackage(pluginName, { deps = [], scope = "project", runtime = "claude" } = {}) {
256
+ export function installPackage(pluginName, { agents = [], deps = [], scope = "project", runtime = "claude" } = {}) {
125
257
  console.log(`\nInstalling @windyroad/${pluginName.replace("wr-", "")} (${scope} scope)...\n`);
126
258
 
127
259
  if (runtime === "claude" || runtime === "both") {
@@ -130,8 +262,7 @@ export function installPackage(pluginName, { deps = [], scope = "project", runti
130
262
  }
131
263
 
132
264
  if (runtime === "codex" || runtime === "both") {
133
- addCodexMarketplace();
134
- installCodexPlugin(pluginName);
265
+ if (!installPackedCodexPlugin(pluginName, { agents, scope })) process.exitCode = 1;
135
266
  }
136
267
 
137
268
  if (deps.length > 0) {
@@ -149,7 +280,7 @@ export function installPackage(pluginName, { deps = [], scope = "project", runti
149
280
  /**
150
281
  * Update a single package.
151
282
  */
152
- export function updatePackage(pluginName, { scope = "project", runtime = "claude" } = {}) {
283
+ export function updatePackage(pluginName, { agents = [], scope = "project", runtime = "claude" } = {}) {
153
284
  console.log(`\nUpdating @windyroad/${pluginName.replace("wr-", "")}...\n`);
154
285
 
155
286
  if (runtime === "claude" || runtime === "both") {
@@ -161,8 +292,7 @@ export function updatePackage(pluginName, { scope = "project", runtime = "claude
161
292
  }
162
293
 
163
294
  if (runtime === "codex" || runtime === "both") {
164
- updateCodexMarketplace();
165
- installCodexPlugin(pluginName);
295
+ if (!installPackedCodexPlugin(pluginName, { agents, scope })) process.exitCode = 1;
166
296
  }
167
297
 
168
298
  console.log(`\nDone! Restart ${runtime === "codex" ? "Codex" : runtime === "both" ? "Claude Code and Codex" : "Claude Code"} to apply updates.\n`);
@@ -171,7 +301,7 @@ export function updatePackage(pluginName, { scope = "project", runtime = "claude
171
301
  /**
172
302
  * Uninstall a single package.
173
303
  */
174
- export function uninstallPackage(pluginName, { runtime = "claude" } = {}) {
304
+ export function uninstallPackage(pluginName, { agents = [], scope = "project", runtime = "claude" } = {}) {
175
305
  console.log(`\nUninstalling @windyroad/${pluginName.replace("wr-", "")}...\n`);
176
306
 
177
307
  if (runtime === "claude" || runtime === "both") {
@@ -179,7 +309,7 @@ export function uninstallPackage(pluginName, { runtime = "claude" } = {}) {
179
309
  }
180
310
 
181
311
  if (runtime === "codex" || runtime === "both") {
182
- uninstallCodexPlugin(pluginName);
312
+ if (!uninstallPackedCodexPlugin(pluginName, { agents, scope })) process.exitCode = 1;
183
313
  }
184
314
 
185
315
  console.log(`\nDone. Restart ${runtime === "codex" ? "Codex" : runtime === "both" ? "Claude Code and Codex" : "Claude Code"} to apply changes.\n`);
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@windyroad/style-guide",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "Style guide enforcement for CSS and UI components",
5
5
  "bin": {
6
6
  "windyroad-style-guide": "./bin/install.mjs"
7
7
  },
8
8
  "type": "module",
9
+ "scripts": {
10
+ "prepack": "node ../../scripts/sync-codex-plugin-surfaces.mjs style-guide --pack",
11
+ "postpack": "node ../../scripts/sync-codex-plugin-surfaces.mjs style-guide --restore-pack"
12
+ },
9
13
  "license": "MIT",
10
14
  "repository": {
11
15
  "type": "git",
@@ -15,6 +19,7 @@
15
19
  "keywords": [
16
20
  "claude-code",
17
21
  "claude-code-plugin",
22
+ "codex",
18
23
  "ai-agent",
19
24
  "ai-coding"
20
25
  ],
@@ -23,7 +28,10 @@
23
28
  "agents/",
24
29
  "hooks/",
25
30
  "skills/",
31
+ "hooks-codex/",
32
+ ".agents/",
26
33
  ".claude-plugin/",
34
+ ".codex-plugin/",
27
35
  "lib/",
28
36
  "!hooks/test/"
29
37
  ]
@@ -1,9 +1,17 @@
1
1
  ---
2
2
  name: wr-style-guide:update-guide
3
3
  description: Create or update the project's docs/STYLE-GUIDE.md by examining existing CSS, components, and design patterns, then asking the user about style preferences.
4
- allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, request_user_input
5
5
  ---
6
6
 
7
+ <!-- Generated from the runtime-neutral skill source. Do not edit. -->
8
+
9
+ > Codex runtime note: invoke installed skills directly, use
10
+ > `request_user_input` only when the contract requires a human decision, and
11
+ > use native Codex subagents for agent delegation. Resolve bundled files from
12
+ > this installed plugin instead of the adopter repository.
13
+
14
+
7
15
  # Style Guide Generator
8
16
 
9
17
  Create or update `docs/STYLE-GUIDE.md` tailored to this project's visual design and component patterns. The style-guide-lead agent reads this file to review CSS and UI component changes.
@@ -73,7 +81,7 @@ Class naming, file naming, component naming patterns.
73
81
 
74
82
  ### 4. Confirm with the user
75
83
 
76
- You MUST use the AskUserQuestion tool to collect user confirmation.
84
+ You MUST use the request_user_input tool to collect user confirmation.
77
85
 
78
86
  Present:
79
87
  1. The CSS architecture summary and ask if it's accurate
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "WR Style Guide: Update Guide"
3
+ short_description: "Update the project style guide"