@windyroad/retrospective 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "wr-retrospective",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Session retrospective reminders and plan review for Claude Code"
5
- }
5
+ }
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # @windyroad/retrospective
2
+
3
+ **Session retrospectives for Claude Code.** Captures learnings at the end of each session and creates problem tickets for failures and friction.
4
+
5
+ Part of [Windy Road Agent Plugins](../../README.md).
6
+
7
+ ## What It Does
8
+
9
+ Every coding session produces learnings -- things that went well, things that broke, things that were harder than expected. Without a retrospective, those learnings evaporate.
10
+
11
+ The retrospective plugin:
12
+
13
+ - **Reminds** you to run a retro when a session ends
14
+ - **Updates** `docs/BRIEFING.md` with session learnings so future sessions start with context
15
+ - **Creates problem tickets** (via [`@windyroad/itil`](../itil/)) for failures and friction encountered during the session
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npx @windyroad/retrospective
21
+ ```
22
+
23
+ Restart Claude Code after installing.
24
+
25
+ > **Requires:** [`@windyroad/itil`](../itil/) and [`@windyroad/risk-scorer`](../risk-scorer/). The installer warns if they're missing.
26
+
27
+ ## Usage
28
+
29
+ **Run a session retrospective:**
30
+
31
+ ```
32
+ /wr-retrospective:run-retro
33
+ ```
34
+
35
+ This walks through the session's work, identifies what went well and what didn't, updates `docs/BRIEFING.md`, and creates problem tickets for any failures.
36
+
37
+ The plugin also triggers a reminder via a `Stop` hook when a session ends naturally.
38
+
39
+ ## How It Works
40
+
41
+ | Hook | Trigger | What it does |
42
+ |------|---------|-------------|
43
+ | `check-deps.sh` | Session start | Verifies that `wr-itil` and `wr-risk-scorer` are installed |
44
+ | `retrospective-reminder.sh` | Session end | Reminds you to run a retrospective |
45
+
46
+ ## Updating and Uninstalling
47
+
48
+ ```bash
49
+ npx @windyroad/retrospective --update
50
+ npx @windyroad/retrospective --uninstall
51
+ ```
52
+
53
+ ## Licence
54
+
55
+ [MIT](../../LICENSE)
package/bin/install.mjs CHANGED
@@ -4,10 +4,10 @@ import { resolve, dirname } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
- const utils = await import(resolve(__dirname, "../../shared/install-utils.mjs"));
7
+ const utils = await import(resolve(__dirname, "../lib/install-utils.mjs"));
8
8
 
9
9
  const PLUGIN = "wr-retrospective";
10
- const DEPS = ["wr-problem", "wr-risk-scorer"];
10
+ const DEPS = ["wr-itil", "wr-risk-scorer"];
11
11
 
12
12
  const flags = utils.parseStandardArgs(process.argv);
13
13
 
@@ -20,6 +20,7 @@ Session retrospectives that update briefings and create problem tickets
20
20
  Options:
21
21
  --update Update this plugin and its skills
22
22
  --uninstall Remove this plugin
23
+ --scope Installation scope: project (default) or user
23
24
  --dry-run Show what would be done without executing
24
25
  --help, -h Show this help
25
26
  `);
@@ -36,7 +37,7 @@ utils.checkPrerequisites();
36
37
  if (flags.uninstall) {
37
38
  utils.uninstallPackage(PLUGIN);
38
39
  } else if (flags.update) {
39
- utils.updatePackage(PLUGIN);
40
+ utils.updatePackage(PLUGIN, { scope: flags.scope });
40
41
  } else {
41
- utils.installPackage(PLUGIN, { deps: DEPS });
42
+ utils.installPackage(PLUGIN, { deps: DEPS, scope: flags.scope });
42
43
  }
package/hooks/hooks.json CHANGED
@@ -1,10 +1,7 @@
1
1
  {
2
2
  "hooks": {
3
3
  "SessionStart": [
4
- { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/bin/check-deps.sh wr-retrospective wr-problem wr-risk-scorer" }] }
5
- ],
6
- "PreToolUse": [
7
- { "matcher": "ExitPlanMode", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/review-plan-enforce.sh" }] }
4
+ { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/bin/check-deps.sh wr-retrospective wr-itil wr-risk-scorer" }] }
8
5
  ],
9
6
  "Stop": [
10
7
  { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/retrospective-reminder.sh" }] }
@@ -0,0 +1,146 @@
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
+
51
+ export function addMarketplace() {
52
+ return run(
53
+ `claude plugin marketplace add ${MARKETPLACE_REPO}`,
54
+ `Marketplace: ${MARKETPLACE_NAME}`
55
+ );
56
+ }
57
+
58
+ export function installPlugin(pluginName, { scope = "project" } = {}) {
59
+ return run(
60
+ `claude plugin install ${pluginName}@${MARKETPLACE_NAME} --scope ${scope}`,
61
+ pluginName
62
+ );
63
+ }
64
+
65
+ export function updatePlugin(pluginName, { scope = "project" } = {}) {
66
+ return run(
67
+ `claude plugin update "${pluginName}@${MARKETPLACE_NAME}" --scope ${scope}`,
68
+ pluginName
69
+ );
70
+ }
71
+
72
+ export function uninstallPlugin(pluginName) {
73
+ return run(`claude plugin uninstall ${pluginName}`, `Removing ${pluginName}`);
74
+ }
75
+
76
+ /**
77
+ * Install a single package: marketplace add + plugin install.
78
+ */
79
+ export function installPackage(pluginName, { deps = [], scope = "project" } = {}) {
80
+ console.log(`\nInstalling @windyroad/${pluginName.replace("wr-", "")} (${scope} scope)...\n`);
81
+
82
+ addMarketplace();
83
+ installPlugin(pluginName, { scope });
84
+
85
+ if (deps.length > 0) {
86
+ console.log(`\nNote: This plugin works best with:`);
87
+ for (const dep of deps) {
88
+ console.log(` - @windyroad/${dep.replace("wr-", "")} (npx @windyroad/${dep.replace("wr-", "")})`);
89
+ }
90
+ }
91
+
92
+ console.log(
93
+ `\nDone! Restart Claude Code to activate.\n`
94
+ );
95
+ }
96
+
97
+ /**
98
+ * Update a single package.
99
+ */
100
+ export function updatePackage(pluginName, { scope = "project" } = {}) {
101
+ console.log(`\nUpdating @windyroad/${pluginName.replace("wr-", "")}...\n`);
102
+
103
+ run(
104
+ `claude plugin marketplace update ${MARKETPLACE_NAME}`,
105
+ "Updating marketplace"
106
+ );
107
+ updatePlugin(pluginName, { scope });
108
+
109
+ console.log("\nDone! Restart Claude Code to apply updates.\n");
110
+ }
111
+
112
+ /**
113
+ * Uninstall a single package.
114
+ */
115
+ export function uninstallPackage(pluginName) {
116
+ console.log(`\nUninstalling @windyroad/${pluginName.replace("wr-", "")}...\n`);
117
+
118
+ uninstallPlugin(pluginName);
119
+
120
+ console.log("\nDone. Restart Claude Code to apply changes.\n");
121
+ }
122
+
123
+ /**
124
+ * Parse standard flags used by all per-plugin installers.
125
+ */
126
+ export function parseStandardArgs(argv) {
127
+ const args = argv.slice(2);
128
+ const flags = {
129
+ help: args.includes("--help") || args.includes("-h"),
130
+ uninstall: args.includes("--uninstall"),
131
+ update: args.includes("--update"),
132
+ dryRun: args.includes("--dry-run"),
133
+ scope: "project",
134
+ };
135
+ const scopeIdx = args.indexOf("--scope");
136
+ if (scopeIdx !== -1 && args[scopeIdx + 1]) {
137
+ const val = args[scopeIdx + 1];
138
+ if (["project", "user", "local"].includes(val)) {
139
+ flags.scope = val;
140
+ } else {
141
+ console.error("--scope requires: project, user, or local");
142
+ process.exit(1);
143
+ }
144
+ }
145
+ return flags;
146
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/retrospective",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Session retrospectives that update briefings and create problem tickets",
5
5
  "bin": {
6
6
  "windyroad-retrospective": "./bin/install.mjs"
@@ -23,6 +23,7 @@
23
23
  "agents/",
24
24
  "hooks/",
25
25
  "skills/",
26
- ".claude-plugin/"
26
+ ".claude-plugin/",
27
+ "lib/"
27
28
  ]
28
29
  }
@@ -0,0 +1,174 @@
1
+ ---
2
+ name: wr-retrospective:run-retro
3
+ description: Run a session retrospective. Updates docs/BRIEFING.md with learnings and creates problem tickets for failures and friction.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Skill
5
+ ---
6
+
7
+ # Session Retrospective
8
+
9
+ Reflect on the current session, update the project briefing, and create problem tickets for failures and friction.
10
+
11
+ ## Steps
12
+
13
+ ### 1. Read the current briefing
14
+
15
+ Read `docs/BRIEFING.md` to understand what previous sessions already captured.
16
+
17
+ ### 2. Reflect on this session
18
+
19
+ Consider the work done in this session and identify:
20
+
21
+ **What you wish you'd been told up front** — things that were non-obvious and caused wasted effort or wrong assumptions. These should be added to BRIEFING.md "What You Need to Know" if they aren't already there.
22
+
23
+ **What surprised you** — things that contradicted reasonable expectations. These should be added to BRIEFING.md "What Will Surprise You" if they aren't already there.
24
+
25
+ **What was harder than it should have been** — friction points, tool limitations, process overhead, confusing code. These should become problem tickets via the `/problem` skill.
26
+
27
+ **What failed** — things that broke, bugs encountered, hooks that errored, tests that failed unexpectedly. These should become problem tickets via the `/problem` skill.
28
+
29
+ **What should we make easier or automate** — repetitive manual steps, missing tooling, things that could be scripted. These should become problem tickets via the `/problem` skill.
30
+
31
+ **What recurring pattern did I (or the assistant) observe that would be better codified?** — a pattern that (a) was invoked multiple times in one session or across sessions, (b) has a deterministic action order or a clear invariant, and (c) is reusable beyond one project. These are **codification candidates** and route through Step 4b below. Do not treat them as problem tickets unless the user explicitly picks that routing option.
32
+
33
+ **What existing skill, agent, hook, ADR, guide, or other codifiable showed a flaw, gap, or friction this session that a targeted edit would fix?** — the **improvement axis** of the codification surface. Criteria: (a) the flaw is reproducible and specific, (b) the fix is a bounded edit to an existing file, (c) no new concept is being invented. Improvement observations flow through the same Step 4b `AskUserQuestion` call as creation candidates, but their options name the improvement shape (e.g. `Skill — improvement stub`, `ADR — supersede or amend`) and the resulting Step 5 row records `Kind: improve` rather than `Kind: create`. An improvement that touches multiple unrelated concerns must be split using the P016 / P017 concern-boundary pattern before routing. If a single output accumulates ≥ 3 improvements in one session, prefer a single coordinating problem ticket over N separate tickets.
34
+
35
+ For each codification candidate, also identify the **Kind** (`create` for a new output, `improve` for a targeted edit to an existing output) and the **best shape** for the codification. The Windy Road suite supports many shapes — pick the one that fits the pattern, not the one you happened to learn first:
36
+
37
+ - **Skill** — deterministic multi-step sequence the user invokes by name (e.g. `wr-itil:ship-fix`). Worked example: `fetch origin → check changesets → score risk → commit → push → release → sync manifest → mark Fix Released`.
38
+ - **Agent** — bounded investigation or review the main agent should delegate to (e.g. a performance-specialist the architect calls in for runtime-path changes). Place under `packages/<plugin>/agents/`.
39
+ - **Hook** — event-driven enforcement or prompt injection (PreToolUse, PostToolUse, UserPromptSubmit). Use when "I keep forgetting to X before Y" — hooks make X unmissable without adding memory load.
40
+ - **Settings entry** — `.claude/settings.json` changes: allowlisted commands, env vars, hook wiring. Best fit when a session repeatedly hits permission prompts for the same benign tool.
41
+ - **Shell or Node script** — reusable repo-level tooling in `scripts/` (e.g. `sync-install-utils.sh`, `sync-plugin-manifests.mjs`). Best fit for multi-step shell sequences worth scripting.
42
+ - **CI step** — `.github/workflows/*.yml` insertion. Best fit for "we'd have caught that earlier with a CI check".
43
+ - **ADR** — architectural decision worth recording. Route to `/wr-architect:create-adr`.
44
+ - **JTBD** — job-to-be-done record for a persona. Route to `/wr-jtbd:update-guide`.
45
+ - **Guide** — voice, style, or risk policy edit. Route to `/wr-voice-tone:update-guide`, `/wr-style-guide:update-guide`, or `/wr-risk-scorer:update-policy`.
46
+ - **Problem ticket** — diagnostic, project-specific friction (the default for flaws). Route to `/wr-itil:manage-problem`.
47
+ - **Test fixture** — regression test for a recurring failure pattern (bats fixture, unit test). Best fit when the observation is "this kept breaking the same way".
48
+ - **Memory** — per-user or per-project memory note in `~/.claude/.../memory/`. Best fit for short, user-habit observations that aren't a codifiable sequence (e.g. "I always forget to run `npm run verify` before pushing").
49
+
50
+ If no shape fits — the observation is a one-off learning, not a repeating pattern — it belongs in BRIEFING.md (Step 3), not Step 4b.
51
+
52
+ Counter-examples (what does **not** become a codification candidate):
53
+ - "The commit gate rejected my work twice because X was misconfigured" — diagnostic, project-specific → **problem ticket** shape (route via Step 4b).
54
+ - "I always forget to run `npm run verify` before pushing" — short, user-habit rather than codifiable sequence → **memory** shape or **BRIEFING.md** note.
55
+
56
+ ### 3. Update BRIEFING.md
57
+
58
+ Edit `docs/BRIEFING.md`:
59
+
60
+ - **Add** new learnings to the appropriate section ("What You Need to Know" or "What Will Surprise You")
61
+ - **Remove** stale items that are no longer true. A learning is stale when:
62
+ - The issue has been fixed (e.g., "CI doesn't test v2" after v2 tests are added)
63
+ - It's now documented elsewhere (e.g., in an ADR, CLAUDE.md, or README)
64
+ - The codebase has changed enough that it's no longer relevant
65
+ - **Update** items where the details have changed
66
+ - Keep the file concise — under 2000 tokens. Each item should be 1-2 lines.
67
+
68
+ Use the AskUserQuestion tool to confirm any removals: "I'd like to remove [item] from BRIEFING.md because [reason]. Is this correct?"
69
+
70
+ ### 4. Create or update problem tickets
71
+
72
+ For each item identified in "What was harder than it should have been", "What failed", and "What should we make easier or automate", use the `/problem` skill to:
73
+
74
+ - Check if a problem ticket already exists in `docs/problems/`
75
+ - If yes: update it with new evidence from this session
76
+ - If no: create a new problem ticket
77
+
78
+ ### 4b. Recommend new codifications
79
+
80
+ For each **codification candidate** identified in Step 2, route the decision through a single `AskUserQuestion` call. This is the ADR-013 Rule 1 structured-interaction pattern — do not present the choices as prose enumeration in the skill output. The shape and Kind identified in Step 2 determine which option rows the user picks from; every shape and Kind routes through the same `AskUserQuestion` so the decision stays one structured interaction (architect decision: flat shape-prefixed options, not a two-step type-then-action or Kind-then-shape flow).
81
+
82
+ For each candidate, invoke `AskUserQuestion` with:
83
+ - `header: "Codification candidate"`
84
+ - `multiSelect: false`
85
+ - Options (a flat list; each option names the shape and Kind up front so the decision is auditable):
86
+
87
+ **Creation axis (Kind: create)** — new outputs:
88
+ 1. `Skill — create stub` — description: "Record a stub candidate (suggested name, scope, triggers, prior uses) for a future scaffolding flow. Skill scaffolding itself is out of scope for this retrospective."
89
+ 2. `Agent — create stub` — description: "Record a stub candidate for a new agent (suggested name, scope, trigger conditions, delegating skill). Place under `packages/<plugin>/agents/` when scaffolded."
90
+ 3. `Hook — create stub` — description: "Record a stub candidate for a new hook (event: PreToolUse / PostToolUse / UserPromptSubmit; trigger; action summary)."
91
+ 4. `Settings — propose entry` — description: "Record a proposed `.claude/settings.json` entry (allowlist / env / hook wiring) for later review."
92
+ 5. `Script — create stub` — description: "Record a stub `scripts/*.sh` or `scripts/*.mjs` candidate (shebang + TODO + scope)."
93
+ 6. `CI — propose step` — description: "Record a proposed `.github/workflows/ci.yml` insertion."
94
+ 7. `ADR — invoke create-adr` — description: "Delegate to `/wr-architect:create-adr` so the decision is captured with proper MADR structure. Routing skill, not a stub."
95
+ 8. `JTBD — invoke update-guide` — description: "Delegate to `/wr-jtbd:update-guide` to add or amend a job-to-be-done record. Routing skill, not a stub."
96
+ 9. `Guide — invoke update-guide / update-policy` — description: "Delegate to `/wr-voice-tone:update-guide`, `/wr-style-guide:update-guide`, or `/wr-risk-scorer:update-policy` depending on the guide touched."
97
+ 10. `Problem — invoke manage-problem` — description: "Delegate to `/wr-itil:manage-problem` so the candidate is WSJF-ranked against other backlog items. Routing skill, not a stub."
98
+ 11. `Test fixture — create stub` — description: "Record a candidate bats / unit-test fixture for the recurring failure pattern."
99
+ 12. `Memory — propose note` — description: "Record a proposed memory note (per-user or per-project) for a short user-habit observation that isn't a codifiable sequence."
100
+
101
+ **Improvement axis (Kind: improve)** — targeted edits to existing outputs (P051):
102
+ 13. `Skill — improvement stub` — description: "Record a proposed targeted edit to an existing skill's SKILL.md (file path, observed flaw, evidence, edit summary). Use when an existing skill has a bounded, reproducible gap."
103
+ 14. `Agent — improvement stub` — description: "Record a proposed targeted edit to an existing agent file (path, observed flaw, edit summary)."
104
+ 15. `Hook — improvement stub` — description: "Record a proposed targeted edit to an existing hook script or `.claude/settings.json` wiring."
105
+ 16. `ADR — supersede or amend` — description: "Delegate to `/wr-architect:create-adr` with a `supersedes ADR-N` hint so the new ADR explicitly replaces or amends the outdated one. Routing skill, not a stub."
106
+ 17. `Guide — improvement edit` — description: "Delegate to `/wr-voice-tone:update-guide`, `/wr-style-guide:update-guide`, `/wr-jtbd:update-guide`, or `/wr-risk-scorer:update-policy` for a targeted edit to an existing guide (voice / style / JTBD / risk policy)."
107
+ 18. `Problem — edit existing ticket` — description: "Delegate to `/wr-itil:manage-problem <NNN>` update flow to amend an existing open or known-error ticket with new observations from this session."
108
+
109
+ **Default:**
110
+ 19. `Skip — not codify-worthy` — description: "Neither stub nor route. The observation is too small, too ambiguous, or a one-off learning that belongs in BRIEFING.md."
111
+
112
+ If a single output has accumulated ≥ 3 improvement candidates in one session, prefer offering a single coordinating ticket (`Problem — invoke manage-problem` with an "apply N improvements to X" scope) over recording N separate improvement stubs — this reduces ticket churn and keeps the affected output's improvement queue coherent.
113
+
114
+ If an improvement candidate touches multiple unrelated concerns, apply the P016 / P017 concern-boundary split before routing: re-run the `AskUserQuestion` once per concern, each with its own shape + Kind selection. This mirrors the concern-boundary analysis used when creating new problem tickets.
115
+
116
+ If the option count is impractical for a single `AskUserQuestion` payload in a given Claude Code version, fall back to a two-question flow: (1) `"Which shape fits?"` with the shape list, (2) `"Create, improve, or skip?"` with `Create stub / Improvement stub / Invoke dedicated skill / Skip` — but prefer the single call when the surface allows it.
117
+
118
+ When the user chooses any of the **Create stub** shapes (skill / agent / hook / settings / script / CI / test / memory), record a candidate entry in the Step 5 summary under "Codification Candidates" with:
119
+ - **Kind** — `create`
120
+ - **Shape** — which codification type (skill, agent, hook, etc.)
121
+ - **Suggested name** — for skills: `wr-<plugin>:<action>`; for agents: `<plugin>:<name>`; for hooks: `<event>:<trigger>`; for scripts: `scripts/<name>.<ext>`; etc.
122
+ - **Scope** — one sentence on what the codification does and when it should fire
123
+ - **Triggers** — example user prompts or events that should invoke it
124
+ - **Prior uses** — 2-3 observed invocations from this session
125
+
126
+ When the user chooses any of the **Improvement stub** shapes (skill / agent / hook), record a candidate entry in the Step 5 summary under "Codification Candidates" with:
127
+ - **Kind** — `improve`
128
+ - **Shape** — which existing codifiable is being edited (skill, agent, hook)
129
+ - **Target file** — the existing file path (e.g. `packages/itil/skills/manage-problem/SKILL.md`)
130
+ - **Observed flaw** — one-sentence description of the gap, friction, or defect
131
+ - **Edit summary** — one-sentence description of the proposed targeted edit
132
+ - **Evidence** — 1-3 observations from this session showing the flaw
133
+
134
+ When the user chooses any of the **Invoke <dedicated skill>** routes (ADR create / JTBD / Guide / Problem) OR the improvement routing options (ADR supersede or amend / Guide improvement edit / Problem edit existing ticket), delegate to the named skill with a context hand-off describing the candidate. Record the routing decision in the Step 5 summary under "Codification Candidates" with Kind (`create` or `improve`), Shape = the routing target, and a `routed to <skill>` marker. For `ADR — supersede or amend`, include the `supersedes ADR-N` hint in the hand-off so create-adr produces the correct MADR header.
135
+
136
+ When the user chooses **Skip**, record the candidate in the Step 5 summary under "Codification Candidates" with a `skipped` marker so the pattern is still visible in the session audit trail.
137
+
138
+ **Non-interactive fallback (per ADR-013 Rule 6):** if `AskUserQuestion` is unavailable, record each candidate in the Step 5 summary under "Codification Candidates" with a `flagged — not actioned (non-interactive)` marker, noting the identified Kind alongside Shape (e.g. `Kind: improve, Shape: skill, flagged — not actioned (non-interactive)`). Do not create stubs, route to dedicated skills, or scaffold. The user can review the flags and decide when they return. Improvement candidates flagged this way retain the Target file and Observed flaw fields so the user has enough to act on without re-deriving the context.
139
+
140
+ **Backward compatibility**: "Skill" is retained as one shape among many so existing P044 muscle memory and `run-retro-skill-candidates.bats` continue to hold. Use the singular shape name in the summary (e.g. `Shape: skill`) so legacy greps still match. Improvement-axis rows use the same singular shape names (`Shape: skill, Kind: improve`) so the Shape column stays consistent across both axes.
141
+
142
+ ### 5. Summary
143
+
144
+ Present a summary to the user:
145
+
146
+ ```
147
+ ## Session Retrospective
148
+
149
+ ### BRIEFING.md Changes
150
+ - Added: [items added]
151
+ - Removed: [items removed with reasons]
152
+ - Updated: [items modified]
153
+
154
+ ### Problems Created/Updated
155
+ - [problem ticket]: [summary]
156
+
157
+ ### Codification Candidates
158
+
159
+ | Kind | Shape | Suggested name / Target file | Scope / Flaw | Triggers / Evidence | Decision |
160
+ |------|-------|-----------------------------|--------------|----------------------|----------|
161
+ | create | skill | [suggested name] | [scope] | [examples] | created stub / routed to <skill> / skipped / flagged (non-interactive) |
162
+ | create | agent | ... | ... | ... | ... |
163
+ | improve | skill | [target file path] | [observed flaw] | [1-3 session observations] | improvement stub / routed to <skill> / skipped / flagged (non-interactive) |
164
+ | improve | hook | ... | ... | ... | ... |
165
+
166
+ ### No Action Needed
167
+ - [learnings that were already captured]
168
+ ```
169
+
170
+ The `Kind` column takes values `create` or `improve` — the create / improve axis defined in Step 2 and Step 4b. Creation rows use the `Suggested name` / `Scope` / `Triggers` field semantics; improvement rows reuse the same columns with `Target file` / `Observed flaw` / `Evidence` semantics (per the stub-recording guidance in Step 4b). The decision column carries the same vocabulary for both Kinds, with `improvement stub` replacing `created stub` for Kind=improve rows.
171
+
172
+ If the "Codification Candidates" table has no rows, omit it rather than rendering an empty header. The legacy "Skill Candidates" heading is preserved as a worked-example row in the Shape column so downstream tooling that grepped for "Skill Candidates" continues to find skill-shaped entries within the unified table.
173
+
174
+ $ARGUMENTS
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env bats
2
+ # Doc-lint guard: run-retro SKILL.md must include a generalised codification
3
+ # branch that recommends agents, hooks, and other codifiable outputs — not
4
+ # only skills. This is the P050 generalisation of P044's single-output-type
5
+ # recommendation surface, extended by P051 with an improvement axis for
6
+ # existing codifiables.
7
+ #
8
+ # Structural assertion — Permitted Exception to the source-grep ban (ADR-005 / P011).
9
+ # These tests assert that the skill specification document includes the
10
+ # multi-shape codification branch introduced by P050 and the improvement-axis
11
+ # extension introduced by P051.
12
+ #
13
+ # Cross-reference:
14
+ # P051: docs/problems/051-run-retro-does-not-recommend-improvements-to-existing-codifiables.open.md
15
+ # P050: docs/problems/050-run-retro-does-not-recommend-other-codifiable-outputs.known-error.md
16
+ # P044: docs/problems/044-run-retro-does-not-recommend-new-skills.known-error.md (predecessor)
17
+ # ADR-013 Rule 1 / Rule 6 (docs/decisions/013-structured-user-interaction-for-governance-decisions.proposed.md)
18
+ # @jtbd JTBD-101 (extend the suite with clear patterns)
19
+ # @jtbd JTBD-006 (progress the backlog while I'm away — AFK-safe Rule 6 fallback)
20
+ # @jtbd JTBD-001 (enforce governance without slowing down)
21
+
22
+ setup() {
23
+ SKILL_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
24
+ SKILL_FILE="${SKILL_DIR}/SKILL.md"
25
+ }
26
+
27
+ @test "SKILL.md Step 2 includes a generalised codification reflection category (P050)" {
28
+ # P050 fix: Step 2 must prompt for recurring patterns that would be better
29
+ # codified — not only as skills. The generalised wording names "codif"
30
+ # (codification / codifiable / codified) so reviewers and agents can tell
31
+ # P050 shipped.
32
+ run grep -in "codification candidate\|codifiable\|codify\|codified" "$SKILL_FILE"
33
+ [ "$status" -eq 0 ]
34
+ }
35
+
36
+ @test "SKILL.md Step 2 names at least three codification shapes beyond skills (P050)" {
37
+ # The shape question is the point of P050. Names at minimum: agent, hook,
38
+ # and one of: settings, script, CI step, ADR, JTBD, guide, test. "skill"
39
+ # stays in the list as a worked example so P044 muscle memory survives.
40
+ run grep -ic "agent" "$SKILL_FILE"
41
+ [ "$status" -eq 0 ]
42
+ [ "$output" -ge 1 ]
43
+ run grep -ic "hook" "$SKILL_FILE"
44
+ [ "$status" -eq 0 ]
45
+ [ "$output" -ge 1 ]
46
+ run grep -icE "settings|script|ci step|ADR|JTBD|guide|test fixture" "$SKILL_FILE"
47
+ [ "$status" -eq 0 ]
48
+ [ "$output" -ge 1 ]
49
+ }
50
+
51
+ @test "SKILL.md Step 4b recommendation branch covers multiple shapes (P050)" {
52
+ # Step 4b must route per-shape. The recommendation branch names more than
53
+ # just "skill" as an output type.
54
+ run grep -inE "(agent|hook).*stub|stub.*(agent|hook)|create.*(agent|hook)|(agent|hook).*candidate" "$SKILL_FILE"
55
+ [ "$status" -eq 0 ]
56
+ }
57
+
58
+ @test "SKILL.md Step 4b uses a single AskUserQuestion with shape-prefixed options (ADR-013 Rule 1)" {
59
+ # Architect decision: flat AskUserQuestion with type-prefixed labels, not a
60
+ # two-step chained flow. At least three shape-prefixed option lines must
61
+ # appear in the Step 4b block. Match both plain and backticked forms:
62
+ # "Skill — ..." / "`Skill — ...`" / "**Skill** — ..."
63
+ run grep -cE "(\`|\*\*)?(Skill|Agent|Hook|Settings|Script|CI|ADR|JTBD|Guide|Problem|Test fixture|Memory)(\`|\*\*)? +(—|-) " "$SKILL_FILE"
64
+ [ "$status" -eq 0 ]
65
+ [ "$output" -ge 3 ]
66
+ }
67
+
68
+ @test "SKILL.md Step 4b routes ADR candidates to wr-architect:create-adr (P050)" {
69
+ # Dedicated codification skills already exist — Step 4b must route to them,
70
+ # not duplicate intake.
71
+ run grep -in "wr-architect:create-adr\|/wr-architect:create-adr" "$SKILL_FILE"
72
+ [ "$status" -eq 0 ]
73
+ }
74
+
75
+ @test "SKILL.md Step 4b routes JTBD candidates to wr-jtbd:update-guide (P050)" {
76
+ run grep -in "wr-jtbd:update-guide\|/wr-jtbd:update-guide" "$SKILL_FILE"
77
+ [ "$status" -eq 0 ]
78
+ }
79
+
80
+ @test "SKILL.md Step 4b preserves non-interactive fallback for generalised shapes (ADR-013 Rule 6)" {
81
+ # The Rule 6 fallback that P044 introduced must cover the generalised surface.
82
+ # When AskUserQuestion is unavailable, all shape candidates are flagged rather
83
+ # than silently chosen.
84
+ run grep -in "non-interactive\|Rule 6" "$SKILL_FILE"
85
+ [ "$status" -eq 0 ]
86
+ }
87
+
88
+ @test "SKILL.md Step 5 summary has a Codification Candidates section with Shape column (P050)" {
89
+ # P050 candidate fix (3): unified table. Either:
90
+ # - a "### Codification Candidates" heading AND a "Shape" column, OR
91
+ # - a "### Codification Candidates" heading with per-shape rows.
92
+ # Accept either shape but require the heading.
93
+ run grep -n "### Codification Candidates\|## Codification Candidates" "$SKILL_FILE"
94
+ [ "$status" -eq 0 ]
95
+ run grep -in "shape" "$SKILL_FILE"
96
+ [ "$status" -eq 0 ]
97
+ }
98
+
99
+ @test "SKILL.md retains 'skill' as a worked example within the generalised category (backward compat with P044)" {
100
+ # Architect advisory: keep 'skill' in the shape list as one worked example so
101
+ # the existing P044 muscle memory and the run-retro-skill-candidates.bats
102
+ # line-30 grep still pass. This is the compatibility test.
103
+ run grep -in "would be better as a skill\|better as a skill\|as a skill\|skill candidate" "$SKILL_FILE"
104
+ [ "$status" -eq 0 ]
105
+ }
106
+
107
+ # ---------------------------------------------------------------------------
108
+ # P051 improvement-axis assertions
109
+ #
110
+ # P051 extends the P050 codification surface with an improvement axis so
111
+ # existing skills, agents, hooks, ADRs, and guides can be recommended for
112
+ # targeted edits (not only new creation). The architect decision requires:
113
+ # - flat shape-prefixed option list (no two-step create-vs-improve question)
114
+ # - parallel naming (e.g. `Skill — improvement stub` mirrors
115
+ # `Skill — create stub`)
116
+ # - Kind column in the Step 5 summary table (create / improve)
117
+ # - non-interactive fallback records the improvement Kind alongside Shape
118
+ # ---------------------------------------------------------------------------
119
+
120
+ @test "SKILL.md Step 2 includes an improvement reflection category for existing codifiables (P051)" {
121
+ # P051 fix: Step 2 must prompt for flaws observed in existing skills /
122
+ # agents / hooks / ADRs / guides — the improvement axis. The generalised
123
+ # phrasing names "improvement" or "improve" alongside "codification" so
124
+ # reviewers and agents can tell P051 shipped.
125
+ run grep -inE "existing (skill|agent|hook|codifiable).*(flaw|friction|gap|improve|improvement)|improvement(-| )shaped|improvement reflection|improvement candidate|improve an existing" "$SKILL_FILE"
126
+ [ "$status" -eq 0 ]
127
+ }
128
+
129
+ @test "SKILL.md Step 4b names improvement-shaped options for multiple shapes (P051)" {
130
+ # P051 fix: the flat option list must carry `Skill — improvement ...`,
131
+ # `Agent — improvement ...`, and `Hook — improvement ...` rows in addition
132
+ # to the create-stub rows introduced by P050. Match at least three
133
+ # shape-prefixed improvement options.
134
+ run grep -cE "(\`|\*\*)?(Skill|Agent|Hook|ADR|Guide|Problem)(\`|\*\*)? +(—|-) +(improvement|supersede|amend|edit)" "$SKILL_FILE"
135
+ [ "$status" -eq 0 ]
136
+ [ "$output" -ge 3 ]
137
+ }
138
+
139
+ @test "SKILL.md Step 4b routes improvement-axis ADR candidates to create-adr with supersede hint (P051)" {
140
+ # ADR improvement shape is "supersede or amend an existing ADR". The
141
+ # routing target stays `/wr-architect:create-adr` (it writes the new ADR
142
+ # with a supersedes reference) — P051 adds the supersede/amend wording as
143
+ # a shape-prefixed option so the improvement axis is recognisable.
144
+ # Require both: an `ADR — supersede` (or equivalent) shape-prefixed option
145
+ # AND a route through `wr-architect:create-adr` near that option.
146
+ run grep -inE "(\`|\*\*)?ADR(\`|\*\*)? +(—|-) +(supersede|amend)" "$SKILL_FILE"
147
+ [ "$status" -eq 0 ]
148
+ }
149
+
150
+ @test "SKILL.md Step 5 summary distinguishes create from improve via a Kind column (P051)" {
151
+ # P051 candidate fix (3): the Codification Candidates table gains a `Kind`
152
+ # column carrying `create` / `improve`. Accept either a literal "Kind"
153
+ # column header or explicit `create / improve` values cited in the summary
154
+ # template. Both forms signal that the summary separates the two axes.
155
+ run grep -inE "\| *Kind *\||Kind column|Kind.*create.*improve|create / improve|create/improve" "$SKILL_FILE"
156
+ [ "$status" -eq 0 ]
157
+ }
158
+
159
+ @test "SKILL.md Step 4b non-interactive fallback covers improvement candidates (P051 + ADR-013 Rule 6)" {
160
+ # Rule 6 fallback must mention the improvement axis explicitly so an AFK
161
+ # loop that flags an improvement candidate records Kind=improve alongside
162
+ # Shape (per architect advisory — the audit trail needs both axes for
163
+ # improvements). Accept either explicit Kind=improve language in the
164
+ # fallback block, OR a statement that the fallback records the Kind
165
+ # alongside Shape.
166
+ run grep -inE "flagged.*improve|improvement.*flagged|Kind.*(Shape|alongside)|(Shape|alongside).*Kind" "$SKILL_FILE"
167
+ [ "$status" -eq 0 ]
168
+ }
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env bats
2
+ # Doc-lint guard: run-retro SKILL.md must include the skill-recommendation branch.
3
+ #
4
+ # Structural assertion — Permitted Exception to the source-grep ban (ADR-005 / P011).
5
+ # These tests do not assert hook behaviour; they assert that the skill specification
6
+ # document includes the skill-candidate branch added in P044.
7
+ #
8
+ # Cross-reference:
9
+ # P044 (docs/problems/044-run-retro-does-not-recommend-new-skills.known-error.md)
10
+ # ADR-013 Rule 1 / Rule 6 (docs/decisions/013-structured-user-interaction-for-governance-decisions.proposed.md)
11
+ # @jtbd JTBD-001 (enforce governance without slowing down)
12
+ # @jtbd JTBD-101 (extend the suite with clear patterns)
13
+
14
+ setup() {
15
+ SKILL_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
16
+ SKILL_FILE="${SKILL_DIR}/SKILL.md"
17
+ }
18
+
19
+ @test "SKILL.md exists and has frontmatter" {
20
+ [ -f "$SKILL_FILE" ]
21
+ run head -1 "$SKILL_FILE"
22
+ [ "$status" -eq 0 ]
23
+ [ "$output" = "---" ]
24
+ }
25
+
26
+ @test "SKILL.md Step 2 includes the skill-candidate reflection category (P044, updated by P050)" {
27
+ # P044 fix: Step 2 must prompt for recurring workflows that would be better
28
+ # as skills. P050 generalises this to a codification category, with "skill"
29
+ # retained as one worked example within the shape list. This test accepts
30
+ # either the original P044 phrasing OR the P050 generalised phrasing that
31
+ # still names "skill" as a shape.
32
+ run grep -in "recurring workflow.*better as a skill\|would be better as a skill\|recurring pattern.*better codified\|\*\*Skill\*\* — " "$SKILL_FILE"
33
+ [ "$status" -eq 0 ]
34
+ }
35
+
36
+ @test "SKILL.md includes a Step 4b Recommend new skills branch (P044)" {
37
+ # P044 fix: a dedicated output branch for skill candidates, distinct from Step 4
38
+ # (problem tickets) and Step 5 (summary).
39
+ run grep -n "Recommend new skills\|Step 4b" "$SKILL_FILE"
40
+ [ "$status" -eq 0 ]
41
+ }
42
+
43
+ @test "SKILL.md Step 4b uses AskUserQuestion (ADR-013 Rule 1)" {
44
+ # ADR-013 Rule 1: the skill-candidate decision branch must use AskUserQuestion,
45
+ # not prose '(a)/(b)/(c)' enumeration. Architect review flagged this as the
46
+ # gotcha to avoid when implementing P044.
47
+ run grep -n "AskUserQuestion" "$SKILL_FILE"
48
+ [ "$status" -eq 0 ]
49
+ }
50
+
51
+ @test "SKILL.md Step 4b header matches ADR-013 structured-interaction pattern (P044, updated by P050)" {
52
+ # P044 used "Skill candidate" as the AskUserQuestion header. P050 generalises
53
+ # to "Codification candidate" with "Skill" as one shape option. Accept either —
54
+ # both preserve the ADR-013 Rule 1 structured-interaction shape.
55
+ run grep -in "Skill candidate\|Codification candidate" "$SKILL_FILE"
56
+ [ "$status" -eq 0 ]
57
+ }
58
+
59
+ @test "SKILL.md Step 4b names the structured options for skill-shaped candidates (P044, updated by P050)" {
60
+ # P044 required three specific option labels (Create a new skill / Track as a
61
+ # problem / Skip — not skill-worthy). P050 generalises: the skill shape now
62
+ # appears as a row in the flat shape-prefixed option list ("Skill — create
63
+ # stub"). The "track as problem" path becomes the explicit "Problem" row.
64
+ # Skip path becomes "Skip — not codify-worthy". This test accepts either
65
+ # pattern so the P044 regression guard survives P050's generalisation.
66
+ run grep -in "Create a new skill\|Skill — create stub\|Skill - create stub" "$SKILL_FILE"
67
+ [ "$status" -eq 0 ]
68
+ run grep -in "Track as a problem ticket\|Problem — invoke manage-problem\|Problem - invoke manage-problem" "$SKILL_FILE"
69
+ [ "$status" -eq 0 ]
70
+ run grep -in "Skip — not skill-worthy\|Skip - not skill-worthy\|Skip — not codify-worthy\|Skip - not codify-worthy" "$SKILL_FILE"
71
+ [ "$status" -eq 0 ]
72
+ }
73
+
74
+ @test "SKILL.md Step 4b has non-interactive fallback per ADR-013 Rule 6" {
75
+ # ADR-013 Rule 6: if AskUserQuestion is unavailable, flag candidates instead of
76
+ # silently choosing. P044 implementation uses "flagged — not actioned" wording.
77
+ run grep -n "non-interactive\|Rule 6" "$SKILL_FILE"
78
+ [ "$status" -eq 0 ]
79
+ }
80
+
81
+ @test "SKILL.md Step 5 summary template has a Skill / Codification Candidates slot (P044, updated by P050)" {
82
+ # P044 fix: the summary template must include a Skill Candidates section so
83
+ # recommendations are visible in the session audit alongside BRIEFING changes
84
+ # and problem tickets. P050 generalises this to a unified "Codification
85
+ # Candidates" table with a Shape column; skill-shaped candidates still appear
86
+ # (as Shape: skill rows). Accept either heading.
87
+ run grep -n "### Skill Candidates\|### Codification Candidates" "$SKILL_FILE"
88
+ [ "$status" -eq 0 ]
89
+ }
90
+
91
+ @test "SKILL.md does not contain 'Options: (a)' prose option list (ADR-013)" {
92
+ # ADR-013 Rule 1: user-facing decisions must use AskUserQuestion, not prose
93
+ # "Options: (a)/(b)/(c)". This matches the narrow pattern used by
94
+ # manage-problem-no-prose-options.bats so criteria lists using (a)/(b)/(c)
95
+ # as internal enumeration (which are fine) don't trip the test.
96
+ run grep -n "Options: (a)" "$SKILL_FILE"
97
+ [ "$status" -ne 0 ]
98
+ }
99
+
100
+ @test "SKILL.md does not contain 'Your call:' prose option prompt (ADR-013)" {
101
+ # ADR-013 Rule 1: mirrors the manage-problem regression guard.
102
+ run grep -n "Your call:" "$SKILL_FILE"
103
+ [ "$status" -ne 0 ]
104
+ }
@@ -1,174 +0,0 @@
1
- #!/bin/bash
2
- # Shared portable helpers for gate enforcement hooks.
3
- # Sourced by architect-gate.sh, risk-gate.sh, and all hook scripts.
4
- # Provides: _mtime, _hashcmd, _doc_exclusions, _err_trap, _get_*
5
-
6
- # ---------------------------------------------------------------------------
7
- # Portable utilities
8
- # ---------------------------------------------------------------------------
9
-
10
- # Portable mtime: tries GNU stat, falls back to macOS stat
11
- _mtime() { stat -c%Y "$1" 2>/dev/null || /usr/bin/stat -f%m "$1" 2>/dev/null || echo 0; }
12
-
13
- # Portable hash: tries md5sum, falls back to md5 -r, then shasum
14
- _hashcmd() { md5sum 2>/dev/null || md5 -r 2>/dev/null || shasum 2>/dev/null; }
15
-
16
- # Paths excluded from pipeline state hashing and docs-only detection.
17
- _doc_exclusions() {
18
- echo ':!docs/' ':!.risk-reports/' ':!.changeset/' ':!governance/' ':!.claude/plans/' ':!CLAUDE.md' ':!AGENTS.md' ':!PRINCIPLES.md' ':!DECISION-MANAGEMENT.md' ':!AGENTIC_RISK_REGISTER.md' ':!PROBLEM-MANAGEMENT.md'
19
- }
20
-
21
- # ---------------------------------------------------------------------------
22
- # ERR trap: outputs diagnostic JSON on hook errors (P010)
23
- # Usage: source gate-helpers.sh at top of hook, then call _enable_err_trap
24
- # ---------------------------------------------------------------------------
25
-
26
- _enable_err_trap() {
27
- trap '_err_trap_handler "$BASH_SOURCE" "$LINENO" "$BASH_COMMAND"' ERR
28
- }
29
-
30
- _err_trap_handler() {
31
- local script="$1" line="$2" cmd="$3"
32
- local name
33
- name=$(basename "$script" 2>/dev/null || echo "$script")
34
- # Output diagnostic as systemMessage so it's visible in conversation
35
- cat <<EOF
36
- {
37
- "systemMessage": "Hook error in ${name} at line ${line}: ${cmd}"
38
- }
39
- EOF
40
- }
41
-
42
- # ---------------------------------------------------------------------------
43
- # JSON input parsing: standardised helpers replacing inline python3
44
- # Each reads from _HOOK_INPUT (set by the hook before calling these)
45
- # ---------------------------------------------------------------------------
46
-
47
- # Store hook input for reuse by parsing helpers
48
- _HOOK_INPUT=""
49
-
50
- _parse_input() {
51
- _HOOK_INPUT=$(cat)
52
- }
53
-
54
- _get_tool_name() {
55
- echo "$_HOOK_INPUT" | python3 -c "
56
- import sys, json
57
- try:
58
- data = json.load(sys.stdin)
59
- print(data.get('tool_name', ''))
60
- except:
61
- print('')
62
- " 2>/dev/null || echo ""
63
- }
64
-
65
- _get_session_id() {
66
- echo "$_HOOK_INPUT" | python3 -c "
67
- import sys, json
68
- try:
69
- data = json.load(sys.stdin)
70
- print(data.get('session_id', ''))
71
- except:
72
- print('')
73
- " 2>/dev/null || echo ""
74
- }
75
-
76
- _get_command() {
77
- echo "$_HOOK_INPUT" | python3 -c "
78
- import sys, json
79
- try:
80
- data = json.load(sys.stdin)
81
- print(data.get('tool_input', {}).get('command', ''))
82
- except:
83
- print('')
84
- " 2>/dev/null || echo ""
85
- }
86
-
87
- _get_file_path() {
88
- echo "$_HOOK_INPUT" | python3 -c "
89
- import sys, json
90
- try:
91
- data = json.load(sys.stdin)
92
- ti = data.get('tool_input', {})
93
- print(ti.get('file_path', ti.get('path', '')))
94
- except:
95
- print('')
96
- " 2>/dev/null || echo ""
97
- }
98
-
99
- _get_subagent_type() {
100
- echo "$_HOOK_INPUT" | python3 -c "
101
- import sys, json
102
- try:
103
- data = json.load(sys.stdin)
104
- print(data.get('tool_input', {}).get('subagent_type', ''))
105
- except:
106
- print('')
107
- " 2>/dev/null || echo ""
108
- }
109
-
110
- _get_user_prompt() {
111
- echo "$_HOOK_INPUT" | python3 -c "
112
- import sys, json
113
- try:
114
- data = json.load(sys.stdin)
115
- print(data.get('user_prompt', ''))
116
- except:
117
- print('')
118
- " 2>/dev/null || echo ""
119
- }
120
-
121
- _get_tool_output() {
122
- echo "$_HOOK_INPUT" | python3 -c "
123
- import sys, json
124
- try:
125
- data = json.load(sys.stdin)
126
- # PostToolUse provides tool_response (dict with content array), not tool_output
127
- tr = data.get('tool_response', {})
128
- if isinstance(tr, dict):
129
- content = tr.get('content', [])
130
- if isinstance(content, list):
131
- texts = [c.get('text', '') for c in content if isinstance(c, dict) and c.get('type') == 'text']
132
- if texts:
133
- print('\n'.join(texts))
134
- sys.exit(0)
135
- # Fallback for older/different hook formats
136
- print(data.get('tool_output', ''))
137
- except:
138
- print('')
139
- " 2>/dev/null || echo ""
140
- }
141
-
142
- # ---------------------------------------------------------------------------
143
- # Session-scoped tmp directory for risk files
144
- # ---------------------------------------------------------------------------
145
-
146
- # Returns the session-scoped directory for risk temp files.
147
- # Creates the directory if it doesn't exist.
148
- # Usage: DIR=$(_risk_dir "$SESSION_ID"); echo "1" > "$DIR/commit"
149
- _risk_dir() {
150
- local sid="$1"
151
- local dir="${TMPDIR:-/tmp}/claude-risk-${sid}"
152
- mkdir -p "$dir"
153
- echo "$dir"
154
- }
155
-
156
- # ---------------------------------------------------------------------------
157
- # Non-doc file detection for WIP gating
158
- # ---------------------------------------------------------------------------
159
-
160
- _is_doc_file() {
161
- local file_path="$1"
162
- local EXCL
163
- EXCL=$(_doc_exclusions)
164
- for pattern in $EXCL; do
165
- local clean="${pattern#:!}"
166
- case "$file_path" in
167
- *"$clean"*) return 0 ;;
168
- esac
169
- done
170
- case "$file_path" in
171
- *.claude/*|*.risk-reports/*|*RISK-POLICY.md) return 0 ;;
172
- esac
173
- return 1
174
- }
@@ -1,102 +0,0 @@
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
- }
@@ -1,73 +0,0 @@
1
- #!/bin/bash
2
- # PreToolUse hook: Denies ExitPlanMode until review specialists have
3
- # reviewed the plan. Skips UI specialists (a11y, voice-tone, style-guide,
4
- # jtbd) when the plan only touches non-UI files (P008 optimization).
5
-
6
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
- source "$SCRIPT_DIR/lib/review-gate.sh"
8
- source "$SCRIPT_DIR/lib/gate-helpers.sh"
9
-
10
- INPUT=$(cat)
11
-
12
- SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') || true
13
-
14
- if [ -z "$SESSION_ID" ]; then
15
- review_gate_parse_error
16
- exit 0
17
- fi
18
-
19
- # Detect if the plan touches UI files by checking uncommitted changes
20
- # UI patterns: *.html, *.jsx, *.tsx, *.vue, *.svelte, *.astro, *.css, *.scss
21
- HAS_UI_FILES=false
22
- UI_PATTERNS='\.html$|\.jsx$|\.tsx$|\.vue$|\.svelte$|\.astro$|\.css$|\.scss$|\.ejs$|\.hbs$|\.erb$|\.leaf$'
23
- if git diff --cached --name-only 2>/dev/null | grep -qE "$UI_PATTERNS"; then
24
- HAS_UI_FILES=true
25
- elif git diff --name-only 2>/dev/null | grep -qE "$UI_PATTERNS"; then
26
- HAS_UI_FILES=true
27
- elif git ls-files --others --exclude-standard 2>/dev/null | grep -qE "$UI_PATTERNS"; then
28
- HAS_UI_FILES=true
29
- fi
30
-
31
- # Also check the plan file itself for mentions of UI files
32
- PLAN_DIR="$HOME/.claude/plans"
33
- if [ -d "$PLAN_DIR" ] && [ "$HAS_UI_FILES" = false ]; then
34
- LATEST_PLAN=$(ls -t "$PLAN_DIR"/*.md 2>/dev/null | head -1)
35
- if [ -n "$LATEST_PLAN" ] && grep -qiE '\.html|\.jsx|\.tsx|\.vue|\.svelte|\.css|component|page|form|modal|dialog' "$LATEST_PLAN" 2>/dev/null; then
36
- HAS_UI_FILES=true
37
- fi
38
- fi
39
-
40
- MISSING=""
41
-
42
- if [ "$HAS_UI_FILES" = true ]; then
43
- # UI files detected — require all specialists
44
- for SYSTEM in a11y voice-tone style-guide jtbd; do
45
- MARKER="/tmp/${SYSTEM}-plan-reviewed-${SESSION_ID}"
46
- if [ ! -f "$MARKER" ]; then
47
- case "$SYSTEM" in
48
- a11y) AGENT="accessibility-agents:accessibility-lead" ;;
49
- voice-tone) AGENT="voice-and-tone-lead" ;;
50
- style-guide) AGENT="style-guide-lead" ;;
51
- jtbd) AGENT="jtbd-lead" ;;
52
- esac
53
- if [ -z "$MISSING" ]; then
54
- MISSING="$AGENT"
55
- else
56
- MISSING="${MISSING}, ${AGENT}"
57
- fi
58
- fi
59
- done
60
- else
61
- # No UI files — skip a11y, voice-tone, style-guide, jtbd
62
- # Auto-create their markers so the gate passes
63
- for SYSTEM in a11y voice-tone style-guide jtbd; do
64
- touch "/tmp/${SYSTEM}-plan-reviewed-${SESSION_ID}"
65
- done
66
- fi
67
-
68
- if [ -n "$MISSING" ]; then
69
- review_gate_deny "BLOCKED: Cannot approve plan without specialist review. Missing: ${MISSING}. Delegate to each agent to review the plan."
70
- exit 0
71
- fi
72
-
73
- exit 0
@@ -1,72 +0,0 @@
1
- ---
2
- name: wr:retrospective
3
- description: Run a session retrospective. Updates docs/BRIEFING.md with learnings and creates problem tickets for failures and friction.
4
- allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Skill
5
- ---
6
-
7
- # Session Retrospective
8
-
9
- Reflect on the current session, update the project briefing, and create problem tickets for failures and friction.
10
-
11
- ## Steps
12
-
13
- ### 1. Read the current briefing
14
-
15
- Read `docs/BRIEFING.md` to understand what previous sessions already captured.
16
-
17
- ### 2. Reflect on this session
18
-
19
- Consider the work done in this session and identify:
20
-
21
- **What you wish you'd been told up front** — things that were non-obvious and caused wasted effort or wrong assumptions. These should be added to BRIEFING.md "What You Need to Know" if they aren't already there.
22
-
23
- **What surprised you** — things that contradicted reasonable expectations. These should be added to BRIEFING.md "What Will Surprise You" if they aren't already there.
24
-
25
- **What was harder than it should have been** — friction points, tool limitations, process overhead, confusing code. These should become problem tickets via the `/problem` skill.
26
-
27
- **What failed** — things that broke, bugs encountered, hooks that errored, tests that failed unexpectedly. These should become problem tickets via the `/problem` skill.
28
-
29
- **What should we make easier or automate** — repetitive manual steps, missing tooling, things that could be scripted. These should become problem tickets via the `/problem` skill.
30
-
31
- ### 3. Update BRIEFING.md
32
-
33
- Edit `docs/BRIEFING.md`:
34
-
35
- - **Add** new learnings to the appropriate section ("What You Need to Know" or "What Will Surprise You")
36
- - **Remove** stale items that are no longer true. A learning is stale when:
37
- - The issue has been fixed (e.g., "CI doesn't test v2" after v2 tests are added)
38
- - It's now documented elsewhere (e.g., in an ADR, CLAUDE.md, or README)
39
- - The codebase has changed enough that it's no longer relevant
40
- - **Update** items where the details have changed
41
- - Keep the file concise — under 2000 tokens. Each item should be 1-2 lines.
42
-
43
- Use the AskUserQuestion tool to confirm any removals: "I'd like to remove [item] from BRIEFING.md because [reason]. Is this correct?"
44
-
45
- ### 4. Create or update problem tickets
46
-
47
- For each item identified in "What was harder than it should have been", "What failed", and "What should we make easier or automate", use the `/problem` skill to:
48
-
49
- - Check if a problem ticket already exists in `docs/problems/`
50
- - If yes: update it with new evidence from this session
51
- - If no: create a new problem ticket
52
-
53
- ### 5. Summary
54
-
55
- Present a summary to the user:
56
-
57
- ```
58
- ## Session Retrospective
59
-
60
- ### BRIEFING.md Changes
61
- - Added: [items added]
62
- - Removed: [items removed with reasons]
63
- - Updated: [items modified]
64
-
65
- ### Problems Created/Updated
66
- - [problem ticket]: [summary]
67
-
68
- ### No Action Needed
69
- - [learnings that were already captured]
70
- ```
71
-
72
- $ARGUMENTS