@windyroad/jtbd 0.13.3 → 0.14.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/.agents/plugins/marketplace.json +1 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +42 -0
- package/bin/install.mjs +6 -4
- package/hooks/hooks.json +61 -7
- package/hooks-codex/codex-adapter.sh +27 -0
- package/hooks-codex/hooks.json +73 -0
- package/lib/install-utils.mjs +138 -8
- package/package.json +9 -1
- package/skills/confirm-jobs-and-personas/SKILL.md +16 -8
- package/skills/confirm-jobs-and-personas/agents/openai.yaml +3 -0
- package/skills/review-jobs/SKILL.md +13 -5
- package/skills/review-jobs/agents/openai.yaml +3 -0
- package/skills/update-guide/SKILL.md +14 -6
- package/skills/update-guide/agents/openai.yaml +3 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"name":"windyroad-jtbd-local","interface":{"displayName":"Windy Road JTBD"},"plugins":[{"name":"wr-jtbd","source":{"source":"local","path":"."},"policy":{"installation":"AVAILABLE","authentication":"ON_INSTALL"},"category":"Developer Tools"}]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wr-jtbd",
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "Jobs-to-be-done governance for product changes",
|
|
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/jtbd",
|
|
10
|
+
"repository": "https://github.com/windyroad/agent-plugins",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"jtbd",
|
|
14
|
+
"product",
|
|
15
|
+
"personas",
|
|
16
|
+
"governance",
|
|
17
|
+
"codex",
|
|
18
|
+
"claude-code"
|
|
19
|
+
],
|
|
20
|
+
"skills": "./skills/",
|
|
21
|
+
"interface": {
|
|
22
|
+
"displayName": "Windy Road JTBD",
|
|
23
|
+
"shortDescription": "Govern changes with jobs and personas",
|
|
24
|
+
"longDescription": "Review product changes against documented jobs and personas, update the JTBD guide, and confirm human oversight.",
|
|
25
|
+
"developerName": "Windy Road Technology",
|
|
26
|
+
"category": "Developer Tools",
|
|
27
|
+
"capabilities": [
|
|
28
|
+
"Interactive",
|
|
29
|
+
"Read",
|
|
30
|
+
"Write"
|
|
31
|
+
],
|
|
32
|
+
"websiteURL": "https://windyroad.com.au",
|
|
33
|
+
"privacyPolicyURL": "https://windyroad.com.au/privacy",
|
|
34
|
+
"termsOfServiceURL": "https://windyroad.com.au/terms",
|
|
35
|
+
"defaultPrompt": [
|
|
36
|
+
"Review these changes against our jobs and personas",
|
|
37
|
+
"Update the JTBD guide"
|
|
38
|
+
],
|
|
39
|
+
"brandColor": "#7C3AED",
|
|
40
|
+
"screenshots": []
|
|
41
|
+
}
|
|
42
|
+
}
|
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-jtbd";
|
|
10
10
|
const DEPS = [];
|
|
11
|
+
const AGENTS = [{ source: "agents/agent.md", name: "wr-jtbd:agent", filename: "wr-jtbd-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,19 +1,73 @@
|
|
|
1
1
|
{
|
|
2
2
|
"hooks": {
|
|
3
3
|
"SessionStart": [
|
|
4
|
-
{
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-oversight-nudge.sh"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
5
13
|
],
|
|
6
14
|
"UserPromptSubmit": [
|
|
7
|
-
{
|
|
8
|
-
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-eval.sh"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"hooks": [
|
|
25
|
+
{
|
|
26
|
+
"type": "command",
|
|
27
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/staleness-check.sh"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
9
31
|
],
|
|
10
32
|
"PreToolUse": [
|
|
11
|
-
{
|
|
12
|
-
|
|
33
|
+
{
|
|
34
|
+
"matcher": "Edit|Write",
|
|
35
|
+
"hooks": [
|
|
36
|
+
{
|
|
37
|
+
"type": "command",
|
|
38
|
+
"command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/jtbd-enforce-edit.sh\""
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"matcher": "Edit|Write",
|
|
44
|
+
"hooks": [
|
|
45
|
+
{
|
|
46
|
+
"type": "command",
|
|
47
|
+
"command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/jtbd-oversight-marker-discipline.sh\""
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
13
51
|
],
|
|
14
52
|
"PostToolUse": [
|
|
15
|
-
{
|
|
16
|
-
|
|
53
|
+
{
|
|
54
|
+
"matcher": "Agent",
|
|
55
|
+
"hooks": [
|
|
56
|
+
{
|
|
57
|
+
"type": "command",
|
|
58
|
+
"command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/jtbd-mark-reviewed.sh\""
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"matcher": "Agent|Bash|Skill",
|
|
64
|
+
"hooks": [
|
|
65
|
+
{
|
|
66
|
+
"type": "command",
|
|
67
|
+
"command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/jtbd-slide-marker.sh\""
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
17
71
|
]
|
|
18
72
|
}
|
|
19
73
|
}
|
|
@@ -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,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-oversight-nudge.sh"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"UserPromptSubmit": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-eval.sh"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"hooks": [
|
|
25
|
+
{
|
|
26
|
+
"type": "command",
|
|
27
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/staleness-check.sh"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"PreToolUse": [
|
|
33
|
+
{
|
|
34
|
+
"matcher": "Edit|Write",
|
|
35
|
+
"hooks": [
|
|
36
|
+
{
|
|
37
|
+
"type": "command",
|
|
38
|
+
"command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/jtbd-enforce-edit.sh\""
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"matcher": "Edit|Write",
|
|
44
|
+
"hooks": [
|
|
45
|
+
{
|
|
46
|
+
"type": "command",
|
|
47
|
+
"command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/jtbd-oversight-marker-discipline.sh\""
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"PostToolUse": [
|
|
53
|
+
{
|
|
54
|
+
"matcher": "Agent",
|
|
55
|
+
"hooks": [
|
|
56
|
+
{
|
|
57
|
+
"type": "command",
|
|
58
|
+
"command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/jtbd-mark-reviewed.sh\""
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"matcher": "Agent|Bash|Skill",
|
|
64
|
+
"hooks": [
|
|
65
|
+
{
|
|
66
|
+
"type": "command",
|
|
67
|
+
"command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/jtbd-slide-marker.sh\""
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|
package/lib/install-utils.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/jtbd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Jobs-to-be-done enforcement for UI changes",
|
|
5
5
|
"bin": {
|
|
6
6
|
"windyroad-jtbd": "./bin/install.mjs"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"prepack": "node ../../scripts/sync-codex-plugin-surfaces.mjs jtbd --pack",
|
|
11
|
+
"postpack": "node ../../scripts/sync-codex-plugin-surfaces.mjs jtbd --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,8 +28,11 @@
|
|
|
23
28
|
"agents/",
|
|
24
29
|
"hooks/",
|
|
25
30
|
"skills/",
|
|
31
|
+
"hooks-codex/",
|
|
26
32
|
"scripts/",
|
|
27
33
|
".claude-plugin/",
|
|
34
|
+
".agents/",
|
|
35
|
+
".codex-plugin/",
|
|
28
36
|
"lib/",
|
|
29
37
|
"!agents/eval/",
|
|
30
38
|
"!agents/test/",
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wr-jtbd:confirm-jobs-and-personas
|
|
3
|
-
description: Drain the set of Jobs To Be Done and personas that lack human oversight. Surfaces each auto-derived job/persona via
|
|
4
|
-
allowed-tools: Read, Glob, Grep, Bash, Edit,
|
|
3
|
+
description: Drain the set of Jobs To Be Done and personas that lack human oversight. Surfaces each auto-derived job/persona via request_user_input so a human confirms, amends, or rejects it, then writes the human-oversight marker. Use when the session-start nudge reports jobs/personas lack oversight, or any time you want to review the documented JTBD corpus. This is the read-write oversight drain — distinct from /wr-jtbd:review-jobs (the read-only alignment reviewer).
|
|
4
|
+
allowed-tools: Read, Glob, Grep, Bash, Edit, 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
|
# Confirm Jobs and Personas — human-oversight drain
|
|
8
16
|
|
|
9
|
-
Lift auto-derived Jobs To Be Done and personas to human decisions. Documented jobs/personas are load-bearing: the JTBD edit gate reviews every project change against `docs/jtbd/`, so a job or persona that was agent-derived without a human confirming it reflects real need propagates wrong alignment verdicts. This skill drains the **unoversighted set** (jobs/personas lacking `human-oversight: confirmed`, per ADR-068): it surfaces each via `
|
|
17
|
+
Lift auto-derived Jobs To Be Done and personas to human decisions. Documented jobs/personas are load-bearing: the JTBD edit gate reviews every project change against `docs/jtbd/`, so a job or persona that was agent-derived without a human confirming it reflects real need propagates wrong alignment verdicts. This skill drains the **unoversighted set** (jobs/personas lacking `human-oversight: confirmed`, per ADR-068): it surfaces each via `request_user_input`, and writes the oversight marker only when a human confirms.
|
|
10
18
|
|
|
11
19
|
This is the P288 / ADR-068 drain surface — the JTBD sibling of `/wr-architect:review-decisions`. It is **read-write** (writes the marker on confirm). It is NOT the same as `/wr-jtbd:review-jobs`, which is a read-only alignment review ("do my changes trace to documented jobs?"); this skill confirms the jobs/personas themselves.
|
|
12
20
|
|
|
@@ -33,9 +41,9 @@ The `wr-jtbd-detect-unoversighted` command is a `$PATH`-resolved shim (ADR-049)
|
|
|
33
41
|
|
|
34
42
|
Read **only the frontmatter + title + Job Statement / persona "Who" section** of each unoversighted file (not full bodies — keep it cheap). Group by persona directory and order **load-bearing first**: personas before their jobs (a persona frames its jobs), and jobs that other artifacts (problems, RFCs, READMEs) cite most heavily before narrow ones.
|
|
35
43
|
|
|
36
|
-
### Step 3: Present each via
|
|
44
|
+
### Step 3: Present each via request_user_input (batched)
|
|
37
45
|
|
|
38
|
-
For each job/persona in the ordered queue, surface it as an `
|
|
46
|
+
For each job/persona in the ordered queue, surface it as an `request_user_input` (cap **4 per call** per ADR-013 Rule 1; issue further calls sequentially):
|
|
39
47
|
|
|
40
48
|
- **Question**: the job statement (for a JTBD) or the persona definition (who they are + key constraints), in one line.
|
|
41
49
|
- **Context**: grounded in what the file actually says (per ADR-026) — the persona served, the desired outcomes, any cited problems/RFCs.
|
|
@@ -45,19 +53,19 @@ For each job/persona in the ordered queue, surface it as an `AskUserQuestion` (c
|
|
|
45
53
|
- **Reject** — the auto-derived job/persona does not reflect real need; capture the supersede ticket (see Step 4) and write the **rejected-pending-supersede** marker so the drain stops re-asking.
|
|
46
54
|
- **Defer** — skip this sitting; leave unoversighted for later.
|
|
47
55
|
|
|
48
|
-
**Presentation rule — lead with the job statement / persona definition, never with the meta (P302).** The
|
|
56
|
+
**Presentation rule — lead with the job statement / persona definition, never with the meta (P302).** The request_user_input `question` field MUST open with the one-line job statement (for a JTBD) or persona definition (for a persona) — *what the job/persona asserts about real user/business need* — phrased as `"This job statement: <statement>"` / `"This persona: <who they are + key constraint>"`. Persona-cluster relationships, source-citations (which problem/RFC triggered the auto-derivation), and any internal-housekeeping recording-shape are **meta** about *how the artifact was authored*, not what it asserts — relegate them to a trailing clause or omit. Mirrors the `/wr-architect:review-decisions` presentation rule (P302) — ADR-074's *name the substance, not the grain* principle applied to the JTBD-confirm surface, and ADR-026 grounding extended from the file body to the request_user_input `question` text. Burying the substance behind meta forces a clarifying re-ask: the user cannot tell what they are confirming reflects real need.
|
|
49
57
|
|
|
50
58
|
The trailing clause exists for cross-reference value — it is optional, NOT a re-entry point for meta-leading. When the meta does not add load-bearing context for the confirm decision, omit it entirely.
|
|
51
59
|
|
|
52
60
|
**Brief-before-ID discipline (P350).** The `question` and `options` text MUST inline what each referenced artefact is and what is at stake before naming it by ID. `JTBD-NNN` / `P-NNN` / `ADR-NNN` / `RFC-NNN` references are audit-trail annotations, NEVER carriers of meaning — the user reads this prompt without project filesystem access (mobile clients, accessibility tooling, notification surfaces) and cannot follow links. Acceptable: *"This job statement: developers want governance enforced automatically so they get manual-review safety without the overhead (cited by the AFK-orchestration backlog ticket)."* Unacceptable: *"This job statement: enforce governance without slowing down (cited by P256, sibling to JTBD-006)."* Trailing parenthetical IDs are permitted ONLY after a self-contained explanation, never as the explanation itself. Mirrors the canonical `/wr-architect:create-adr` Step 5 § 5a Rule 3 ("No IDs as explainers"). See also session memory `feedback_brief_before_id.md`.
|
|
53
61
|
|
|
54
|
-
This is a genuine human-decision surface (the point of P288/ADR-068) — `
|
|
62
|
+
This is a genuine human-decision surface (the point of P288/ADR-068) — `request_user_input` is correct here, not over-asking. Do not auto-confirm; do not prose-ask.
|
|
55
63
|
|
|
56
64
|
### Step 4: Apply the outcome
|
|
57
65
|
|
|
58
66
|
- **Confirm / Amend**: write `human-oversight: confirmed` + `oversight-date: <today, YYYY-MM-DD>` into the file's frontmatter (insert after the `status:`/`date-created:` line if absent; never duplicate). For Amend, apply the directed change first. Edits go through the standard JTBD / architect edit gate per ADR-014.
|
|
59
67
|
- **Reject** (ADR-068 amendment per P316, mirroring ADR-066):
|
|
60
|
-
1. Capture the supersede ticket via a follow-up `
|
|
68
|
+
1. Capture the supersede ticket via a follow-up `request_user_input`: "Which problem ticket tracks the rework?" — options: existing `P<NNN>` IDs surfaced from `docs/problems/`, **Capture a new ticket** (delegate to `/wr-itil:capture-problem`), or **Defer (leave un-tracked for now)**.
|
|
61
69
|
2. If a ticket ID is captured, write `human-oversight: rejected-pending-supersede` + `supersede-ticket: P<NNN>` into the file's frontmatter. The detector excludes artifacts carrying both, so the drain stops re-asking until either the rework lands (file renamed to `*.superseded.md`) or the rejection is revisited.
|
|
62
70
|
3. If the user defers ticket capture, leave the marker absent — the artifact re-surfaces next drain (the un-tracked case is intentionally re-asked so it doesn't silently rot).
|
|
63
71
|
- **Defer**: no write.
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wr-jtbd:review-jobs
|
|
3
3
|
description: On-demand JTBD alignment review. Checks staged changes and recent commits against documented persona jobs in docs/jtbd/. Use before a release or when adding new features.
|
|
4
|
-
allowed-tools: Read, Glob, Grep, Bash,
|
|
4
|
+
allowed-tools: Read, Glob, Grep, Bash, request_user_input, Skill
|
|
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
|
# JTBD Alignment Review Skill
|
|
8
16
|
|
|
9
17
|
Run a Jobs To Be Done alignment review on demand — outside the pre-tool-use hook gate. Reviews staged changes and recent commits against the documented persona jobs in `docs/jtbd/`.
|
|
@@ -49,7 +57,7 @@ Summarise:
|
|
|
49
57
|
|
|
50
58
|
### 3. Resolve ambiguity
|
|
51
59
|
|
|
52
|
-
If there are no staged changes and no recent unpushed commits, use `
|
|
60
|
+
If there are no staged changes and no recent unpushed commits, use `request_user_input` to ask:
|
|
53
61
|
|
|
54
62
|
> "I don't see any staged or unpushed changes. What would you like me to review?
|
|
55
63
|
> (a) A specific set of files or a planned feature — please describe it
|
|
@@ -68,10 +76,10 @@ Build a self-contained prompt for the JTBD subagent that includes:
|
|
|
68
76
|
|
|
69
77
|
### 5. Delegate to wr-jtbd:agent
|
|
70
78
|
|
|
71
|
-
Invoke the JTBD subagent via the
|
|
79
|
+
Invoke the JTBD subagent via the installed skill invocation:
|
|
72
80
|
|
|
73
81
|
```
|
|
74
|
-
|
|
82
|
+
agent_type: wr-jtbd:agent
|
|
75
83
|
prompt: <constructed review prompt from step 4>
|
|
76
84
|
```
|
|
77
85
|
|
|
@@ -85,7 +93,7 @@ Present the full alignment report to the user. The JTBD subagent will report:
|
|
|
85
93
|
- BREAKS: changes that appear to remove or degrade a documented job outcome
|
|
86
94
|
- NEW JOB NEEDED: capabilities being added that serve an undocumented need
|
|
87
95
|
|
|
88
|
-
If gaps or breaks are identified, use `
|
|
96
|
+
If gaps or breaks are identified, use `request_user_input` to ask how the user wants to proceed:
|
|
89
97
|
- (a) Document the new job before continuing (recommended)
|
|
90
98
|
- (b) Proceed with a documented exception
|
|
91
99
|
- (c) Revise the approach to serve an existing job
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wr-jtbd:update-guide
|
|
3
3
|
description: Create or update the project's docs/jtbd/ directory with per-persona directories and individual job files. Migrates from docs/JOBS_TO_BE_DONE.md if present.
|
|
4
|
-
allowed-tools: Read, Write, Edit, Bash, Glob, Grep,
|
|
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
|
# Jobs To Be Done Document Generator
|
|
8
16
|
|
|
9
17
|
Create or update `docs/jtbd/` with per-persona directories and individual job files.
|
|
@@ -84,12 +92,12 @@ Use kebab-case for the directory name (e.g., `developer`, `tech-lead`).
|
|
|
84
92
|
|
|
85
93
|
### 4. Confirm personas with the user
|
|
86
94
|
|
|
87
|
-
Use
|
|
95
|
+
Use request_user_input to present the drafted personas and ask:
|
|
88
96
|
- Are these the right personas?
|
|
89
97
|
- Any missing user segments?
|
|
90
98
|
- Any constraints or pain points to add?
|
|
91
99
|
|
|
92
|
-
**Born-confirmed write (ADR-068 — structurally gated by ADR-110).** Once the user confirms a persona via this
|
|
100
|
+
**Born-confirmed write (ADR-068 — structurally gated by ADR-110).** Once the user confirms a persona via this request_user_input pass, IMMEDIATELY call the marker-evidence helper THEN insert the two lines into that persona's frontmatter — after the `description:` line:
|
|
93
101
|
|
|
94
102
|
```bash
|
|
95
103
|
wr-jtbd-mark-oversight-confirmed docs/jtbd/<persona-name>/persona.md
|
|
@@ -100,7 +108,7 @@ human-oversight: confirmed
|
|
|
100
108
|
oversight-date: YYYY-MM-DD # today
|
|
101
109
|
```
|
|
102
110
|
|
|
103
|
-
The `wr-jtbd-mark-oversight-confirmed` call writes the session-scoped evidence marker (`/tmp/oversight-confirmed-<sha>-<sid>`) that the `jtbd-oversight-marker-discipline.sh` PreToolUse hook reads to authorise the subsequent Edit/Write — without the helper call, the hook will DENY the marker write. This is the load-bearing born-confirmed gate: a persona authored through update-guide enters the world human-oversighted ONLY because the helper above paired the user's substance-confirm answer to the marker write. Do NOT write the marker for a persona the user has not confirmed. AFK iter subprocesses spawned via `
|
|
111
|
+
The `wr-jtbd-mark-oversight-confirmed` call writes the session-scoped evidence marker (`/tmp/oversight-confirmed-<sha>-<sid>`) that the `jtbd-oversight-marker-discipline.sh` PreToolUse hook reads to authorise the subsequent Edit/Write — without the helper call, the hook will DENY the marker write. This is the load-bearing born-confirmed gate: a persona authored through update-guide enters the world human-oversighted ONLY because the helper above paired the user's substance-confirm answer to the marker write. Do NOT write the marker for a persona the user has not confirmed. AFK iter subprocesses spawned via `native Codex subagent` have no `request_user_input` access; they MUST write `human-oversight: unconfirmed` (the AFK fallback enum value codified in ADR-110), which the drain (`/wr-jtbd:confirm-jobs-and-personas`) later promotes interactively. The marker is orthogonal to status.
|
|
104
112
|
|
|
105
113
|
### 5. Draft jobs
|
|
106
114
|
|
|
@@ -143,12 +151,12 @@ by user research or production use. To validate: rename the file from
|
|
|
143
151
|
|
|
144
152
|
### 6. Confirm jobs with the user
|
|
145
153
|
|
|
146
|
-
Use
|
|
154
|
+
Use request_user_input to present the drafted jobs and ask:
|
|
147
155
|
- Do these jobs cover the core value proposition?
|
|
148
156
|
- Do the job statements ring true?
|
|
149
157
|
- Any missing jobs or user flows?
|
|
150
158
|
|
|
151
|
-
**Born-confirmed write (ADR-068 — structurally gated by ADR-110).** Once the user confirms a job via this
|
|
159
|
+
**Born-confirmed write (ADR-068 — structurally gated by ADR-110).** Once the user confirms a job via this request_user_input pass, IMMEDIATELY call the marker-evidence helper THEN insert the two lines into that job's frontmatter — after the `date-created:` line:
|
|
152
160
|
|
|
153
161
|
```bash
|
|
154
162
|
wr-jtbd-mark-oversight-confirmed docs/jtbd/<persona-name>/JTBD-NNN-<kebab-title>.proposed.md
|