@sienklogic/plan-build-run 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/package.json +5 -5
- package/plugins/copilot-pbr/plugin.json +1 -1
- package/plugins/copilot-pbr/skills/statusline/SKILL.md +145 -0
- package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-pbr/skills/statusline/SKILL.md +146 -0
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/commands/statusline.md +5 -0
- package/plugins/pbr/scripts/check-dangerous-commands.js +44 -0
- package/plugins/pbr/scripts/check-skill-workflow.js +80 -3
- package/plugins/pbr/scripts/check-subagent-output.js +64 -1
- package/plugins/pbr/scripts/validate-task.js +218 -3
- package/plugins/pbr/skills/statusline/SKILL.md +147 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ All notable changes to Plan-Build-Run will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.6.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.5.0...plan-build-run-v2.6.0) (2026-02-19)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **01-01:** add build and plan executor gates to validate-task.js ([4d882e0](https://github.com/SienkLogic/plan-build-run/commit/4d882e07d9560c0540c2277149338137a9a7e05d))
|
|
14
|
+
* **01-01:** extend agent output validation to all 10 PBR agent types ([9f4384f](https://github.com/SienkLogic/plan-build-run/commit/9f4384fa2391c3e5905243119da5bebbf65f6218))
|
|
15
|
+
* **01-02:** add skill-specific workflow rules and CRITICAL enforcement ([173e89e](https://github.com/SienkLogic/plan-build-run/commit/173e89e0dfc81aa425b222efd982b83a19e2b3d0))
|
|
16
|
+
* **tools:** add /pbr:statusline command to install PBR status line ([8bd9e7a](https://github.com/SienkLogic/plan-build-run/commit/8bd9e7a98b76cf8e1686eb7a936da8539fe20a08))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* **01-01:** hasPlanFile now matches numbered plan files like PLAN-01.md ([00c4af8](https://github.com/SienkLogic/plan-build-run/commit/00c4af8066c4c0c24f25be7cd6731acb2b13cb61))
|
|
22
|
+
* **tools:** prefix unused name var with underscore in version sync test ([8b8b81d](https://github.com/SienkLogic/plan-build-run/commit/8b8b81dea5eff86fb4503cecdc9e677f573faf03))
|
|
23
|
+
* **tools:** resolve lint errors in statusline workflow rules ([6c32db7](https://github.com/SienkLogic/plan-build-run/commit/6c32db7947ccaf392457750a26406ca92a3eef77))
|
|
24
|
+
* **tools:** revert release branch CI trigger (using non-strict protection instead) ([836ac24](https://github.com/SienkLogic/plan-build-run/commit/836ac2401d3381b395fcf6b2bf252ff78745abd5))
|
|
25
|
+
* **tools:** trigger CI on release-please branch pushes for auto-merge ([443e046](https://github.com/SienkLogic/plan-build-run/commit/443e0466f27eb51269999755eb2f8d37093d0f65))
|
|
26
|
+
|
|
8
27
|
## [2.5.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.4.1...plan-build-run-v2.5.0) (2026-02-19)
|
|
9
28
|
|
|
10
29
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sienklogic/plan-build-run",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Plan it, Build it, Run it — structured development workflow for Claude Code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
],
|
|
48
48
|
"coverageThreshold": {
|
|
49
49
|
"global": {
|
|
50
|
-
"statements":
|
|
51
|
-
"branches":
|
|
52
|
-
"functions":
|
|
53
|
-
"lines":
|
|
50
|
+
"statements": 60,
|
|
51
|
+
"branches": 55,
|
|
52
|
+
"functions": 60,
|
|
53
|
+
"lines": 60
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pbr",
|
|
3
3
|
"displayName": "Plan-Build-Run",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.6.0",
|
|
5
5
|
"description": "Plan-Build-Run — Structured development workflow for GitHub Copilot CLI. Solves context rot through disciplined agent delegation, structured planning, atomic execution, and goal-backward verification.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "SienkLogic",
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: statusline
|
|
3
|
+
description: "Install or configure the PBR status line in Claude Code."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
**STOP — DO NOT READ THIS FILE. You are already reading it. This prompt was injected into your context by Claude Code's plugin system. Using the Read tool on this SKILL.md file wastes ~3,000 tokens. Begin executing Step 0 immediately.**
|
|
7
|
+
|
|
8
|
+
## Step 0 — Immediate Output
|
|
9
|
+
|
|
10
|
+
**Before ANY tool calls**, display this banner:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
14
|
+
PLAN-BUILD-RUN ► STATUS LINE
|
|
15
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Then proceed to Step 1.
|
|
19
|
+
|
|
20
|
+
# /pbr:statusline — Status Line Setup
|
|
21
|
+
|
|
22
|
+
The PBR status line displays live project state (phase, plan, status, git branch, context usage) in the Claude Code terminal status bar.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Subcommand Parsing
|
|
27
|
+
|
|
28
|
+
Parse `$ARGUMENTS`:
|
|
29
|
+
|
|
30
|
+
| Argument | Action |
|
|
31
|
+
|----------|--------|
|
|
32
|
+
| `install` or empty | Install/enable the status line |
|
|
33
|
+
| `uninstall` or `remove` | Remove the status line configuration |
|
|
34
|
+
| `preview` | Show what the status line looks like without installing |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Subcommand: install (default)
|
|
39
|
+
|
|
40
|
+
### Step 1: Locate the status-line script
|
|
41
|
+
|
|
42
|
+
**CRITICAL: You must resolve the correct absolute path to `status-line.js`. Do NOT hardcode paths.**
|
|
43
|
+
|
|
44
|
+
1. The script lives at `${PLUGIN_ROOT}/scripts/status-line.js`
|
|
45
|
+
2. Resolve `${PLUGIN_ROOT}` to its absolute path using `pwd` or by checking the plugin root
|
|
46
|
+
3. If running from a local plugin dir (`claude --plugin-dir .`), the path is the local repo's `plugins/pbr/scripts/status-line.js`
|
|
47
|
+
4. If running from the installed plugin cache (`~/.claude/plugins/cache/`), use that path
|
|
48
|
+
5. **Verify the script exists** with `ls` before proceeding. If it doesn't exist, show an error and stop.
|
|
49
|
+
|
|
50
|
+
Store the resolved absolute path as `SCRIPT_PATH`.
|
|
51
|
+
|
|
52
|
+
### Step 2: Read current settings
|
|
53
|
+
|
|
54
|
+
Read `~/.claude/settings.json` (or `$HOME/.claude/settings.json`).
|
|
55
|
+
|
|
56
|
+
- If the file doesn't exist: start with an empty object `{}`
|
|
57
|
+
- If it exists: parse the JSON content
|
|
58
|
+
- Check if `statusLine` key already exists:
|
|
59
|
+
- If yes and points to the same script: inform user "PBR status line is already installed." and stop (unless they want to reconfigure)
|
|
60
|
+
- If yes but points to a different command: warn user and ask if they want to replace it
|
|
61
|
+
|
|
62
|
+
### Step 3: Configure settings.json
|
|
63
|
+
|
|
64
|
+
Use AskUserQuestion:
|
|
65
|
+
question: "Install the PBR status line? This adds a `statusLine` entry to ~/.claude/settings.json."
|
|
66
|
+
header: "Install?"
|
|
67
|
+
options:
|
|
68
|
+
- label: "Install" description: "Enable the PBR status line in Claude Code"
|
|
69
|
+
- label: "Preview first" description: "Show a preview before installing"
|
|
70
|
+
- label: "Cancel" description: "Don't install"
|
|
71
|
+
multiSelect: false
|
|
72
|
+
|
|
73
|
+
If "Preview first": run the preview subcommand (show sample output), then ask again.
|
|
74
|
+
If "Cancel": stop.
|
|
75
|
+
If "Install":
|
|
76
|
+
|
|
77
|
+
**CRITICAL: Use Read tool to read the file, then Write to update it. Do NOT use sed or other text manipulation on JSON files.**
|
|
78
|
+
|
|
79
|
+
1. Read `~/.claude/settings.json`
|
|
80
|
+
2. Parse the JSON
|
|
81
|
+
3. Set `statusLine` to:
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"type": "command",
|
|
85
|
+
"command": "node \"SCRIPT_PATH\""
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
Where `SCRIPT_PATH` is the resolved absolute path from Step 1. Use forward slashes even on Windows.
|
|
89
|
+
4. Write the updated JSON back (preserve all other settings, use 2-space indentation)
|
|
90
|
+
|
|
91
|
+
### Step 4: Verify and confirm
|
|
92
|
+
|
|
93
|
+
Display:
|
|
94
|
+
```
|
|
95
|
+
✓ PBR status line installed
|
|
96
|
+
|
|
97
|
+
Script: {SCRIPT_PATH}
|
|
98
|
+
Config: ~/.claude/settings.json
|
|
99
|
+
|
|
100
|
+
The status line will appear on your next Claude Code session.
|
|
101
|
+
Restart Claude Code or run `/clear` to activate it now.
|
|
102
|
+
|
|
103
|
+
Customize per-project via .planning/config.json:
|
|
104
|
+
"status_line": {
|
|
105
|
+
"sections": ["phase", "plan", "status", "git", "context"],
|
|
106
|
+
"brand_text": "PBR"
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Subcommand: uninstall
|
|
113
|
+
|
|
114
|
+
1. Read `~/.claude/settings.json`
|
|
115
|
+
2. If no `statusLine` key: inform user "No status line configured." and stop
|
|
116
|
+
3. Remove the `statusLine` key from the JSON
|
|
117
|
+
4. Write the updated file
|
|
118
|
+
5. Display: `✓ PBR status line removed. Restart Claude Code to take effect.`
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Subcommand: preview
|
|
123
|
+
|
|
124
|
+
1. Locate and run the status-line script: `node {SCRIPT_PATH}`
|
|
125
|
+
- Pass sample stdin JSON: `{"context_window": {"used_percentage": 35}, "model": {"display_name": "Claude Opus 4.6"}, "cost": {"total_cost_usd": 0.42}}`
|
|
126
|
+
2. Display the raw output to the user
|
|
127
|
+
3. Also show a description of each section:
|
|
128
|
+
- **Phase**: Current phase number and name from STATE.md
|
|
129
|
+
- **Plan**: Plan progress (N of M)
|
|
130
|
+
- **Status**: Phase status keyword (planning, building, built, etc.)
|
|
131
|
+
- **Git**: Current branch + dirty indicator
|
|
132
|
+
- **Context**: Unicode bar showing context window usage (green/yellow/red)
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Edge Cases
|
|
137
|
+
|
|
138
|
+
### No .planning/ directory
|
|
139
|
+
The status line works even without `.planning/` — it will show only git and context sections. Installation doesn't require a PBR project.
|
|
140
|
+
|
|
141
|
+
### Plugin installed from npm vs local
|
|
142
|
+
The script path differs between `~/.claude/plugins/cache/plan-build-run/pbr/{version}/scripts/status-line.js` and a local `plugins/pbr/scripts/status-line.js`. The install command must resolve the actual path at install time.
|
|
143
|
+
|
|
144
|
+
### Existing non-PBR status line
|
|
145
|
+
If `statusLine` already exists with a different command, warn the user and confirm before replacing.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pbr",
|
|
3
3
|
"displayName": "Plan-Build-Run",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.6.0",
|
|
5
5
|
"description": "Plan-Build-Run — Structured development workflow for Cursor. Solves context rot through disciplined subagent delegation, structured planning, atomic execution, and goal-backward verification.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "SienkLogic",
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: statusline
|
|
3
|
+
description: "Install or configure the PBR status line in Claude Code."
|
|
4
|
+
argument-hint: "[install | uninstall | preview]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**STOP — DO NOT READ THIS FILE. You are already reading it. This prompt was injected into your context by Claude Code's plugin system. Using the Read tool on this SKILL.md file wastes ~3,000 tokens. Begin executing Step 0 immediately.**
|
|
8
|
+
|
|
9
|
+
## Step 0 — Immediate Output
|
|
10
|
+
|
|
11
|
+
**Before ANY tool calls**, display this banner:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
15
|
+
PLAN-BUILD-RUN ► STATUS LINE
|
|
16
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then proceed to Step 1.
|
|
20
|
+
|
|
21
|
+
# /pbr:statusline — Status Line Setup
|
|
22
|
+
|
|
23
|
+
The PBR status line displays live project state (phase, plan, status, git branch, context usage) in the Claude Code terminal status bar.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Subcommand Parsing
|
|
28
|
+
|
|
29
|
+
Parse `$ARGUMENTS`:
|
|
30
|
+
|
|
31
|
+
| Argument | Action |
|
|
32
|
+
|----------|--------|
|
|
33
|
+
| `install` or empty | Install/enable the status line |
|
|
34
|
+
| `uninstall` or `remove` | Remove the status line configuration |
|
|
35
|
+
| `preview` | Show what the status line looks like without installing |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Subcommand: install (default)
|
|
40
|
+
|
|
41
|
+
### Step 1: Locate the status-line script
|
|
42
|
+
|
|
43
|
+
**CRITICAL: You must resolve the correct absolute path to `status-line.js`. Do NOT hardcode paths.**
|
|
44
|
+
|
|
45
|
+
1. The script lives at `${PLUGIN_ROOT}/scripts/status-line.js`
|
|
46
|
+
2. Resolve `${PLUGIN_ROOT}` to its absolute path using `pwd` or by checking the plugin root
|
|
47
|
+
3. If running from a local plugin dir (`claude --plugin-dir .`), the path is the local repo's `plugins/pbr/scripts/status-line.js`
|
|
48
|
+
4. If running from the installed plugin cache (`~/.claude/plugins/cache/`), use that path
|
|
49
|
+
5. **Verify the script exists** with `ls` before proceeding. If it doesn't exist, show an error and stop.
|
|
50
|
+
|
|
51
|
+
Store the resolved absolute path as `SCRIPT_PATH`.
|
|
52
|
+
|
|
53
|
+
### Step 2: Read current settings
|
|
54
|
+
|
|
55
|
+
Read `~/.claude/settings.json` (or `$HOME/.claude/settings.json`).
|
|
56
|
+
|
|
57
|
+
- If the file doesn't exist: start with an empty object `{}`
|
|
58
|
+
- If it exists: parse the JSON content
|
|
59
|
+
- Check if `statusLine` key already exists:
|
|
60
|
+
- If yes and points to the same script: inform user "PBR status line is already installed." and stop (unless they want to reconfigure)
|
|
61
|
+
- If yes but points to a different command: warn user and ask if they want to replace it
|
|
62
|
+
|
|
63
|
+
### Step 3: Configure settings.json
|
|
64
|
+
|
|
65
|
+
Use AskUserQuestion:
|
|
66
|
+
question: "Install the PBR status line? This adds a `statusLine` entry to ~/.claude/settings.json."
|
|
67
|
+
header: "Install?"
|
|
68
|
+
options:
|
|
69
|
+
- label: "Install" description: "Enable the PBR status line in Claude Code"
|
|
70
|
+
- label: "Preview first" description: "Show a preview before installing"
|
|
71
|
+
- label: "Cancel" description: "Don't install"
|
|
72
|
+
multiSelect: false
|
|
73
|
+
|
|
74
|
+
If "Preview first": run the preview subcommand (show sample output), then ask again.
|
|
75
|
+
If "Cancel": stop.
|
|
76
|
+
If "Install":
|
|
77
|
+
|
|
78
|
+
**CRITICAL: Use Read tool to read the file, then Write to update it. Do NOT use sed or other text manipulation on JSON files.**
|
|
79
|
+
|
|
80
|
+
1. Read `~/.claude/settings.json`
|
|
81
|
+
2. Parse the JSON
|
|
82
|
+
3. Set `statusLine` to:
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"type": "command",
|
|
86
|
+
"command": "node \"SCRIPT_PATH\""
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
Where `SCRIPT_PATH` is the resolved absolute path from Step 1. Use forward slashes even on Windows.
|
|
90
|
+
4. Write the updated JSON back (preserve all other settings, use 2-space indentation)
|
|
91
|
+
|
|
92
|
+
### Step 4: Verify and confirm
|
|
93
|
+
|
|
94
|
+
Display:
|
|
95
|
+
```
|
|
96
|
+
✓ PBR status line installed
|
|
97
|
+
|
|
98
|
+
Script: {SCRIPT_PATH}
|
|
99
|
+
Config: ~/.claude/settings.json
|
|
100
|
+
|
|
101
|
+
The status line will appear on your next Claude Code session.
|
|
102
|
+
Restart Claude Code or run `/clear` to activate it now.
|
|
103
|
+
|
|
104
|
+
Customize per-project via .planning/config.json:
|
|
105
|
+
"status_line": {
|
|
106
|
+
"sections": ["phase", "plan", "status", "git", "context"],
|
|
107
|
+
"brand_text": "PBR"
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Subcommand: uninstall
|
|
114
|
+
|
|
115
|
+
1. Read `~/.claude/settings.json`
|
|
116
|
+
2. If no `statusLine` key: inform user "No status line configured." and stop
|
|
117
|
+
3. Remove the `statusLine` key from the JSON
|
|
118
|
+
4. Write the updated file
|
|
119
|
+
5. Display: `✓ PBR status line removed. Restart Claude Code to take effect.`
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Subcommand: preview
|
|
124
|
+
|
|
125
|
+
1. Locate and run the status-line script: `node {SCRIPT_PATH}`
|
|
126
|
+
- Pass sample stdin JSON: `{"context_window": {"used_percentage": 35}, "model": {"display_name": "Claude Opus 4.6"}, "cost": {"total_cost_usd": 0.42}}`
|
|
127
|
+
2. Display the raw output to the user
|
|
128
|
+
3. Also show a description of each section:
|
|
129
|
+
- **Phase**: Current phase number and name from STATE.md
|
|
130
|
+
- **Plan**: Plan progress (N of M)
|
|
131
|
+
- **Status**: Phase status keyword (planning, building, built, etc.)
|
|
132
|
+
- **Git**: Current branch + dirty indicator
|
|
133
|
+
- **Context**: Unicode bar showing context window usage (green/yellow/red)
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Edge Cases
|
|
138
|
+
|
|
139
|
+
### No .planning/ directory
|
|
140
|
+
The status line works even without `.planning/` — it will show only git and context sections. Installation doesn't require a PBR project.
|
|
141
|
+
|
|
142
|
+
### Plugin installed from npm vs local
|
|
143
|
+
The script path differs between `~/.claude/plugins/cache/plan-build-run/pbr/{version}/scripts/status-line.js` and a local `plugins/pbr/scripts/status-line.js`. The install command must resolve the actual path at install time.
|
|
144
|
+
|
|
145
|
+
### Existing non-PBR status line
|
|
146
|
+
If `statusLine` already exists with a different command, warn the user and confirm before replacing.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pbr",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Plan-Build-Run — Structured development workflow for Claude Code. Solves context rot through disciplined subagent delegation, structured planning, atomic execution, and goal-backward verification.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SienkLogic",
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* 2 = blocked (destructive command detected)
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
22
24
|
const { logHook } = require('./hook-logger');
|
|
23
25
|
|
|
24
26
|
// Commands that are outright blocked
|
|
@@ -107,10 +109,52 @@ function checkDangerous(data) {
|
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
// Skill-specific checks
|
|
113
|
+
const skillResult = checkSkillSpecificBash(command);
|
|
114
|
+
if (skillResult) return skillResult;
|
|
115
|
+
|
|
110
116
|
// No match — allow
|
|
111
117
|
return null;
|
|
112
118
|
}
|
|
113
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Skill-specific bash command checks.
|
|
122
|
+
* Currently: statusline skill cannot use sed/awk/perl on JSON files.
|
|
123
|
+
*/
|
|
124
|
+
function checkSkillSpecificBash(command) {
|
|
125
|
+
const planningDir = path.join(process.cwd(), '.planning');
|
|
126
|
+
const skillFile = path.join(planningDir, '.active-skill');
|
|
127
|
+
|
|
128
|
+
let activeSkill = null;
|
|
129
|
+
try {
|
|
130
|
+
activeSkill = fs.readFileSync(skillFile, 'utf8').trim();
|
|
131
|
+
} catch (_e) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (activeSkill !== 'statusline') return null;
|
|
136
|
+
|
|
137
|
+
// Block sed/awk/perl targeting .json files
|
|
138
|
+
const jsonManipPattern = /\b(sed|awk|perl)\b.*\.json/;
|
|
139
|
+
const echoRedirectPattern = /echo\s.*>\s*.*\.json/;
|
|
140
|
+
|
|
141
|
+
if (jsonManipPattern.test(command) || echoRedirectPattern.test(command)) {
|
|
142
|
+
logHook('check-dangerous-commands', 'PreToolUse', 'block', {
|
|
143
|
+
command: command.substring(0, 200),
|
|
144
|
+
reason: 'JSON shell manipulation during statusline'
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
output: {
|
|
148
|
+
decision: 'block',
|
|
149
|
+
reason: 'CRITICAL: Use Read + Write tools for JSON files, not shell text manipulation. Shell tools can corrupt JSON structure.'
|
|
150
|
+
},
|
|
151
|
+
exitCode: 2
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
114
158
|
function main() {
|
|
115
159
|
let input = '';
|
|
116
160
|
|
|
@@ -114,6 +114,12 @@ function checkSkillRules(skill, filePath, planningDir) {
|
|
|
114
114
|
return checkQuickRules(filePath, isInPlanning, planningDir);
|
|
115
115
|
case 'build':
|
|
116
116
|
return checkBuildRules(filePath, isInPlanning, planningDir);
|
|
117
|
+
case 'statusline':
|
|
118
|
+
return checkStatuslineRules(filePath, isInPlanning, planningDir);
|
|
119
|
+
case 'review':
|
|
120
|
+
case 'discuss':
|
|
121
|
+
case 'begin':
|
|
122
|
+
return checkReadOnlySkillRules(skill, filePath, isInPlanning);
|
|
117
123
|
default:
|
|
118
124
|
return null;
|
|
119
125
|
}
|
|
@@ -205,6 +211,63 @@ function checkBuildRules(filePath, isInPlanning, planningDir) {
|
|
|
205
211
|
}
|
|
206
212
|
}
|
|
207
213
|
|
|
214
|
+
/**
|
|
215
|
+
* /pbr:statusline rules:
|
|
216
|
+
* - Warn when writing settings.json with hardcoded home directory paths
|
|
217
|
+
*/
|
|
218
|
+
function checkStatuslineRules(filePath, _isInPlanning, _planningDir) {
|
|
219
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
220
|
+
|
|
221
|
+
// Only check settings.json writes
|
|
222
|
+
if (!normalizedPath.endsWith('settings.json')) return null;
|
|
223
|
+
|
|
224
|
+
// Check tool_input content isn't available here — we only have filePath.
|
|
225
|
+
// The hardcoded path check needs content, which we get from the hook data.
|
|
226
|
+
// This function is called from checkSkillRules which only passes filePath.
|
|
227
|
+
// We'll check in the wrapper instead. For now, return null (pass).
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Extended statusline check that includes content inspection.
|
|
233
|
+
* Called from checkWorkflow/main where we have access to full hook data.
|
|
234
|
+
*/
|
|
235
|
+
function checkStatuslineContent(data) {
|
|
236
|
+
const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
|
|
237
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
238
|
+
|
|
239
|
+
if (!normalizedPath.endsWith('settings.json')) return null;
|
|
240
|
+
|
|
241
|
+
// Check new_string (Edit) or content (Write) for hardcoded home paths
|
|
242
|
+
const content = data.tool_input?.new_string || data.tool_input?.content || '';
|
|
243
|
+
const oldString = data.tool_input?.old_string || '';
|
|
244
|
+
const textToCheck = content + ' ' + oldString;
|
|
245
|
+
|
|
246
|
+
// Hardcoded home directory paths — warn, don't block (may be legitimately resolved)
|
|
247
|
+
const hardcodedPathPattern = /(\/home\/|C:\\Users\\|\/Users\/)[^"'\s]*\.claude/i;
|
|
248
|
+
if (hardcodedPathPattern.test(textToCheck)) {
|
|
249
|
+
return {
|
|
250
|
+
rule: 'statusline-hardcoded-path',
|
|
251
|
+
message: `Warning: settings.json write appears to contain a hardcoded home directory path.\n\nFile: ${filePath}\n\nCRITICAL: Do NOT hardcode paths. Use dynamic path resolution to find the correct plugin installation directory.`
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Read-only skill rules (review, discuss, begin):
|
|
260
|
+
* - Cannot write files outside .planning/
|
|
261
|
+
*/
|
|
262
|
+
function checkReadOnlySkillRules(skill, filePath, isInPlanning) {
|
|
263
|
+
if (isInPlanning) return null;
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
rule: `${skill}-readonly`,
|
|
267
|
+
message: `Workflow violation: /pbr:${skill} should only write to .planning/ files.\n\nBlocked: ${filePath}\n\nThe ${skill} skill is not intended to modify source code.`
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
208
271
|
/**
|
|
209
272
|
* Check if any PLAN.md file exists in a directory (recursive one level).
|
|
210
273
|
*/
|
|
@@ -214,10 +277,10 @@ function hasPlanFile(dir) {
|
|
|
214
277
|
try {
|
|
215
278
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
216
279
|
for (const entry of entries) {
|
|
217
|
-
if (entry.isFile() && entry.name
|
|
280
|
+
if (entry.isFile() && /^PLAN.*\.md$/i.test(entry.name)) return true;
|
|
218
281
|
if (entry.isDirectory()) {
|
|
219
282
|
const subEntries = fs.readdirSync(path.join(dir, entry.name));
|
|
220
|
-
if (subEntries.some(f =>
|
|
283
|
+
if (subEntries.some(f => /^PLAN.*\.md$/i.test(f))) return true;
|
|
221
284
|
}
|
|
222
285
|
}
|
|
223
286
|
} catch (_e) {
|
|
@@ -255,8 +318,22 @@ function checkWorkflow(data) {
|
|
|
255
318
|
};
|
|
256
319
|
}
|
|
257
320
|
|
|
321
|
+
// Statusline content check (needs full data for content inspection)
|
|
322
|
+
if (activeSkill === 'statusline') {
|
|
323
|
+
const contentViolation = checkStatuslineContent(data);
|
|
324
|
+
if (contentViolation) {
|
|
325
|
+
logHook('check-skill-workflow', 'PreToolUse', 'warn', {
|
|
326
|
+
skill: activeSkill, file: path.basename(filePath), rule: contentViolation.rule
|
|
327
|
+
});
|
|
328
|
+
return {
|
|
329
|
+
exitCode: 0,
|
|
330
|
+
output: { additionalContext: contentViolation.message }
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
258
335
|
return null;
|
|
259
336
|
}
|
|
260
337
|
|
|
261
|
-
module.exports = { readActiveSkill, checkSkillRules, hasPlanFile, checkWorkflow };
|
|
338
|
+
module.exports = { readActiveSkill, checkSkillRules, hasPlanFile, checkWorkflow, checkStatuslineContent, checkReadOnlySkillRules };
|
|
262
339
|
if (require.main === module || process.argv[1] === __filename) { main(); }
|
|
@@ -53,6 +53,69 @@ const AGENT_OUTPUTS = {
|
|
|
53
53
|
return [];
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
},
|
|
57
|
+
'pbr:synthesizer': {
|
|
58
|
+
description: 'synthesis file in .planning/research/ or CONTEXT.md update',
|
|
59
|
+
check: (planningDir) => {
|
|
60
|
+
const researchDir = path.join(planningDir, 'research');
|
|
61
|
+
if (fs.existsSync(researchDir)) {
|
|
62
|
+
try {
|
|
63
|
+
const files = fs.readdirSync(researchDir).filter(f => f.endsWith('.md'));
|
|
64
|
+
if (files.length > 0) return files.map(f => path.join('research', f));
|
|
65
|
+
} catch (_e) { /* best-effort */ }
|
|
66
|
+
}
|
|
67
|
+
const contextFile = path.join(planningDir, 'CONTEXT.md');
|
|
68
|
+
if (fs.existsSync(contextFile)) {
|
|
69
|
+
try {
|
|
70
|
+
const stat = fs.statSync(contextFile);
|
|
71
|
+
if (stat.size > 0) return ['CONTEXT.md'];
|
|
72
|
+
} catch (_e) { /* best-effort */ }
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
'pbr:plan-checker': {
|
|
78
|
+
description: 'advisory output (no file expected)',
|
|
79
|
+
noFileExpected: true,
|
|
80
|
+
check: () => []
|
|
81
|
+
},
|
|
82
|
+
'pbr:integration-checker': {
|
|
83
|
+
description: 'advisory output (no file expected)',
|
|
84
|
+
noFileExpected: true,
|
|
85
|
+
check: () => []
|
|
86
|
+
},
|
|
87
|
+
'pbr:debugger': {
|
|
88
|
+
description: 'debug file in .planning/debug/',
|
|
89
|
+
check: (planningDir) => {
|
|
90
|
+
const debugDir = path.join(planningDir, 'debug');
|
|
91
|
+
if (!fs.existsSync(debugDir)) return [];
|
|
92
|
+
try {
|
|
93
|
+
return fs.readdirSync(debugDir)
|
|
94
|
+
.filter(f => f.endsWith('.md'))
|
|
95
|
+
.map(f => path.join('debug', f));
|
|
96
|
+
} catch (_e) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
'pbr:codebase-mapper': {
|
|
102
|
+
description: 'codebase map in .planning/codebase/',
|
|
103
|
+
check: (planningDir) => {
|
|
104
|
+
const codebaseDir = path.join(planningDir, 'codebase');
|
|
105
|
+
if (!fs.existsSync(codebaseDir)) return [];
|
|
106
|
+
try {
|
|
107
|
+
return fs.readdirSync(codebaseDir)
|
|
108
|
+
.filter(f => f.endsWith('.md'))
|
|
109
|
+
.map(f => path.join('codebase', f));
|
|
110
|
+
} catch (_e) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
'pbr:general': {
|
|
116
|
+
description: 'advisory output (no file expected)',
|
|
117
|
+
noFileExpected: true,
|
|
118
|
+
check: () => []
|
|
56
119
|
}
|
|
57
120
|
};
|
|
58
121
|
|
|
@@ -157,7 +220,7 @@ function main() {
|
|
|
157
220
|
// Check for expected outputs
|
|
158
221
|
const found = outputSpec.check(planningDir);
|
|
159
222
|
|
|
160
|
-
if (found.length === 0) {
|
|
223
|
+
if (found.length === 0 && !outputSpec.noFileExpected) {
|
|
161
224
|
logHook('check-subagent-output', 'PostToolUse', 'warning', {
|
|
162
225
|
agent_type: agentType,
|
|
163
226
|
expected: outputSpec.description,
|
|
@@ -3,15 +3,22 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* PreToolUse hook: Validates Task() calls before execution.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Blocking checks (exit 2):
|
|
7
|
+
* - When active skill is "quick" and pbr:executor is spawned without
|
|
8
|
+
* a PLAN.md in .planning/quick/{NNN}-{slug}/
|
|
9
|
+
*
|
|
10
|
+
* Advisory checks (exit 0, logs warnings):
|
|
7
11
|
* - description exists and is non-empty
|
|
8
12
|
* - description is reasonably short (<=100 chars)
|
|
9
13
|
* - subagent_type is a known pbr: agent type when applicable
|
|
10
14
|
*
|
|
11
15
|
* Exit codes:
|
|
12
|
-
* 0 =
|
|
16
|
+
* 0 = pass (advisory warnings only)
|
|
17
|
+
* 2 = block (missing quick task PLAN.md)
|
|
13
18
|
*/
|
|
14
19
|
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
15
22
|
const { logHook } = require('./hook-logger');
|
|
16
23
|
|
|
17
24
|
const KNOWN_AGENTS = [
|
|
@@ -75,6 +82,176 @@ function checkTask(data) {
|
|
|
75
82
|
return warnings;
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Blocking check: when the active skill is "quick" and an executor is being
|
|
87
|
+
* spawned, verify that at least one .planning/quick/{NNN}-{slug}/PLAN.md exists.
|
|
88
|
+
* Returns { block: true, reason: "..." } if the executor should be blocked,
|
|
89
|
+
* or null if it's OK to proceed.
|
|
90
|
+
*/
|
|
91
|
+
function checkQuickExecutorGate(data) {
|
|
92
|
+
const toolInput = data.tool_input || {};
|
|
93
|
+
const subagentType = toolInput.subagent_type || '';
|
|
94
|
+
|
|
95
|
+
// Only gate pbr:executor
|
|
96
|
+
if (subagentType !== 'pbr:executor') return null;
|
|
97
|
+
|
|
98
|
+
const cwd = process.cwd();
|
|
99
|
+
const planningDir = path.join(cwd, '.planning');
|
|
100
|
+
const activeSkillFile = path.join(planningDir, '.active-skill');
|
|
101
|
+
|
|
102
|
+
// Only gate when active skill is "quick"
|
|
103
|
+
try {
|
|
104
|
+
const activeSkill = fs.readFileSync(activeSkillFile, 'utf8').trim();
|
|
105
|
+
if (activeSkill !== 'quick') return null;
|
|
106
|
+
} catch (_e) {
|
|
107
|
+
// No .active-skill file — not in a quick task flow
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check for any PLAN.md in .planning/quick/*/
|
|
112
|
+
const quickDir = path.join(planningDir, 'quick');
|
|
113
|
+
if (!fs.existsSync(quickDir)) {
|
|
114
|
+
return {
|
|
115
|
+
block: true,
|
|
116
|
+
reason: 'Cannot spawn executor: .planning/quick/ directory does not exist. ' +
|
|
117
|
+
'You must create the quick task directory and PLAN.md first (Steps 4-6).'
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const dirs = fs.readdirSync(quickDir).filter(d => {
|
|
123
|
+
return /^\d{3}-/.test(d) && fs.statSync(path.join(quickDir, d)).isDirectory();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Look for the most recent quick task dir that has a PLAN.md
|
|
127
|
+
const hasPlan = dirs.some(d => {
|
|
128
|
+
const planFile = path.join(quickDir, d, 'PLAN.md');
|
|
129
|
+
try {
|
|
130
|
+
const stat = fs.statSync(planFile);
|
|
131
|
+
return stat.size > 0;
|
|
132
|
+
} catch (_e) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (!hasPlan) {
|
|
138
|
+
return {
|
|
139
|
+
block: true,
|
|
140
|
+
reason: 'Cannot spawn executor: no PLAN.md found in any .planning/quick/*/ directory. ' +
|
|
141
|
+
'You must create .planning/quick/{NNN}-{slug}/PLAN.md first (Steps 4-6).'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
} catch (_e) {
|
|
145
|
+
return {
|
|
146
|
+
block: true,
|
|
147
|
+
reason: 'Cannot spawn executor: failed to read .planning/quick/ directory.'
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Blocking check: when the active skill is "build" and an executor is being
|
|
156
|
+
* spawned, verify that a PLAN*.md exists in the current phase directory.
|
|
157
|
+
* Returns { block: true, reason: "..." } if blocked, or null if OK.
|
|
158
|
+
*/
|
|
159
|
+
function checkBuildExecutorGate(data) {
|
|
160
|
+
const toolInput = data.tool_input || {};
|
|
161
|
+
const subagentType = toolInput.subagent_type || '';
|
|
162
|
+
|
|
163
|
+
// Only gate pbr:executor
|
|
164
|
+
if (subagentType !== 'pbr:executor') return null;
|
|
165
|
+
|
|
166
|
+
const cwd = process.cwd();
|
|
167
|
+
const planningDir = path.join(cwd, '.planning');
|
|
168
|
+
const activeSkillFile = path.join(planningDir, '.active-skill');
|
|
169
|
+
|
|
170
|
+
// Only gate when active skill is "build"
|
|
171
|
+
try {
|
|
172
|
+
const activeSkill = fs.readFileSync(activeSkillFile, 'utf8').trim();
|
|
173
|
+
if (activeSkill !== 'build') return null;
|
|
174
|
+
} catch (_e) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Read STATE.md for current phase
|
|
179
|
+
const stateFile = path.join(planningDir, 'STATE.md');
|
|
180
|
+
try {
|
|
181
|
+
const state = fs.readFileSync(stateFile, 'utf8');
|
|
182
|
+
const phaseMatch = state.match(/Phase:\s*(\d+)\s+of\s+\d+/);
|
|
183
|
+
if (!phaseMatch) return null;
|
|
184
|
+
|
|
185
|
+
const currentPhase = phaseMatch[1].padStart(2, '0');
|
|
186
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
187
|
+
if (!fs.existsSync(phasesDir)) {
|
|
188
|
+
return {
|
|
189
|
+
block: true,
|
|
190
|
+
reason: 'Cannot spawn executor: .planning/phases/ directory does not exist. Run /pbr:plan first.'
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const dirs = fs.readdirSync(phasesDir).filter(d => d.startsWith(currentPhase + '-'));
|
|
195
|
+
if (dirs.length === 0) {
|
|
196
|
+
return {
|
|
197
|
+
block: true,
|
|
198
|
+
reason: `Cannot spawn executor: no phase directory found for phase ${currentPhase}. Run /pbr:plan first.`
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const phaseDir = path.join(phasesDir, dirs[0]);
|
|
203
|
+
const files = fs.readdirSync(phaseDir);
|
|
204
|
+
const hasPlan = files.some(f => {
|
|
205
|
+
if (!/^PLAN.*\.md$/i.test(f)) return false;
|
|
206
|
+
try {
|
|
207
|
+
return fs.statSync(path.join(phaseDir, f)).size > 0;
|
|
208
|
+
} catch (_e) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
if (!hasPlan) {
|
|
214
|
+
return {
|
|
215
|
+
block: true,
|
|
216
|
+
reason: `Cannot spawn executor: no PLAN.md found in .planning/phases/${dirs[0]}/. Run /pbr:plan first.`
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
} catch (_e) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Blocking check: when the active skill is "plan", block executor spawning.
|
|
228
|
+
* The plan skill should never spawn executors.
|
|
229
|
+
* Returns { block: true, reason: "..." } if blocked, or null if OK.
|
|
230
|
+
*/
|
|
231
|
+
function checkPlanExecutorGate(data) {
|
|
232
|
+
const toolInput = data.tool_input || {};
|
|
233
|
+
const subagentType = toolInput.subagent_type || '';
|
|
234
|
+
|
|
235
|
+
// Only gate pbr:executor
|
|
236
|
+
if (subagentType !== 'pbr:executor') return null;
|
|
237
|
+
|
|
238
|
+
const cwd = process.cwd();
|
|
239
|
+
const planningDir = path.join(cwd, '.planning');
|
|
240
|
+
const activeSkillFile = path.join(planningDir, '.active-skill');
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const activeSkill = fs.readFileSync(activeSkillFile, 'utf8').trim();
|
|
244
|
+
if (activeSkill !== 'plan') return null;
|
|
245
|
+
} catch (_e) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
block: true,
|
|
251
|
+
reason: 'Plan skill should not spawn executors. Use /pbr:build to execute plans.'
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
78
255
|
function main() {
|
|
79
256
|
let input = '';
|
|
80
257
|
|
|
@@ -83,6 +260,44 @@ function main() {
|
|
|
83
260
|
process.stdin.on('end', () => {
|
|
84
261
|
try {
|
|
85
262
|
const data = JSON.parse(input);
|
|
263
|
+
|
|
264
|
+
// Blocking gate: quick executor must have PLAN.md
|
|
265
|
+
const gate = checkQuickExecutorGate(data);
|
|
266
|
+
if (gate && gate.block) {
|
|
267
|
+
logHook('validate-task', 'PreToolUse', 'blocked', { reason: gate.reason });
|
|
268
|
+
process.stdout.write(JSON.stringify({
|
|
269
|
+
decision: 'block',
|
|
270
|
+
reason: gate.reason
|
|
271
|
+
}));
|
|
272
|
+
process.exit(2);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Blocking gate: build executor must have PLAN.md in phase dir
|
|
277
|
+
const buildGate = checkBuildExecutorGate(data);
|
|
278
|
+
if (buildGate && buildGate.block) {
|
|
279
|
+
logHook('validate-task', 'PreToolUse', 'blocked', { reason: buildGate.reason });
|
|
280
|
+
process.stdout.write(JSON.stringify({
|
|
281
|
+
decision: 'block',
|
|
282
|
+
reason: buildGate.reason
|
|
283
|
+
}));
|
|
284
|
+
process.exit(2);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Blocking gate: plan skill cannot spawn executors
|
|
289
|
+
const planGate = checkPlanExecutorGate(data);
|
|
290
|
+
if (planGate && planGate.block) {
|
|
291
|
+
logHook('validate-task', 'PreToolUse', 'blocked', { reason: planGate.reason });
|
|
292
|
+
process.stdout.write(JSON.stringify({
|
|
293
|
+
decision: 'block',
|
|
294
|
+
reason: planGate.reason
|
|
295
|
+
}));
|
|
296
|
+
process.exit(2);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Advisory warnings
|
|
86
301
|
const warnings = checkTask(data);
|
|
87
302
|
|
|
88
303
|
if (warnings.length > 0) {
|
|
@@ -102,5 +317,5 @@ function main() {
|
|
|
102
317
|
});
|
|
103
318
|
}
|
|
104
319
|
|
|
105
|
-
module.exports = { checkTask, KNOWN_AGENTS, MAX_DESCRIPTION_LENGTH };
|
|
320
|
+
module.exports = { checkTask, checkQuickExecutorGate, checkBuildExecutorGate, checkPlanExecutorGate, KNOWN_AGENTS, MAX_DESCRIPTION_LENGTH };
|
|
106
321
|
if (require.main === module || process.argv[1] === __filename) { main(); }
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: statusline
|
|
3
|
+
description: "Install or configure the PBR status line in Claude Code."
|
|
4
|
+
allowed-tools: Read, Write, Bash, AskUserQuestion
|
|
5
|
+
argument-hint: "[install | uninstall | preview]"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**STOP — DO NOT READ THIS FILE. You are already reading it. This prompt was injected into your context by Claude Code's plugin system. Using the Read tool on this SKILL.md file wastes ~3,000 tokens. Begin executing Step 0 immediately.**
|
|
9
|
+
|
|
10
|
+
## Step 0 — Immediate Output
|
|
11
|
+
|
|
12
|
+
**Before ANY tool calls**, display this banner:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
16
|
+
PLAN-BUILD-RUN ► STATUS LINE
|
|
17
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then proceed to Step 1.
|
|
21
|
+
|
|
22
|
+
# /pbr:statusline — Status Line Setup
|
|
23
|
+
|
|
24
|
+
The PBR status line displays live project state (phase, plan, status, git branch, context usage) in the Claude Code terminal status bar.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Subcommand Parsing
|
|
29
|
+
|
|
30
|
+
Parse `$ARGUMENTS`:
|
|
31
|
+
|
|
32
|
+
| Argument | Action |
|
|
33
|
+
|----------|--------|
|
|
34
|
+
| `install` or empty | Install/enable the status line |
|
|
35
|
+
| `uninstall` or `remove` | Remove the status line configuration |
|
|
36
|
+
| `preview` | Show what the status line looks like without installing |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Subcommand: install (default)
|
|
41
|
+
|
|
42
|
+
### Step 1: Locate the status-line script
|
|
43
|
+
|
|
44
|
+
**CRITICAL: You must resolve the correct absolute path to `status-line.js`. Do NOT hardcode paths.**
|
|
45
|
+
|
|
46
|
+
1. The script lives at `${CLAUDE_PLUGIN_ROOT}/scripts/status-line.js`
|
|
47
|
+
2. Resolve `${CLAUDE_PLUGIN_ROOT}` to its absolute path using `pwd` or by checking the plugin root
|
|
48
|
+
3. If running from a local plugin dir (`claude --plugin-dir .`), the path is the local repo's `plugins/pbr/scripts/status-line.js`
|
|
49
|
+
4. If running from the installed plugin cache (`~/.claude/plugins/cache/`), use that path
|
|
50
|
+
5. **Verify the script exists** with `ls` before proceeding. If it doesn't exist, show an error and stop.
|
|
51
|
+
|
|
52
|
+
Store the resolved absolute path as `SCRIPT_PATH`.
|
|
53
|
+
|
|
54
|
+
### Step 2: Read current settings
|
|
55
|
+
|
|
56
|
+
Read `~/.claude/settings.json` (or `$HOME/.claude/settings.json`).
|
|
57
|
+
|
|
58
|
+
- If the file doesn't exist: start with an empty object `{}`
|
|
59
|
+
- If it exists: parse the JSON content
|
|
60
|
+
- Check if `statusLine` key already exists:
|
|
61
|
+
- If yes and points to the same script: inform user "PBR status line is already installed." and stop (unless they want to reconfigure)
|
|
62
|
+
- If yes but points to a different command: warn user and ask if they want to replace it
|
|
63
|
+
|
|
64
|
+
### Step 3: Configure settings.json
|
|
65
|
+
|
|
66
|
+
Use AskUserQuestion:
|
|
67
|
+
question: "Install the PBR status line? This adds a `statusLine` entry to ~/.claude/settings.json."
|
|
68
|
+
header: "Install?"
|
|
69
|
+
options:
|
|
70
|
+
- label: "Install" description: "Enable the PBR status line in Claude Code"
|
|
71
|
+
- label: "Preview first" description: "Show a preview before installing"
|
|
72
|
+
- label: "Cancel" description: "Don't install"
|
|
73
|
+
multiSelect: false
|
|
74
|
+
|
|
75
|
+
If "Preview first": run the preview subcommand (show sample output), then ask again.
|
|
76
|
+
If "Cancel": stop.
|
|
77
|
+
If "Install":
|
|
78
|
+
|
|
79
|
+
**CRITICAL: Use Read tool to read the file, then Write to update it. Do NOT use sed or other text manipulation on JSON files.**
|
|
80
|
+
|
|
81
|
+
1. Read `~/.claude/settings.json`
|
|
82
|
+
2. Parse the JSON
|
|
83
|
+
3. Set `statusLine` to:
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"type": "command",
|
|
87
|
+
"command": "node \"SCRIPT_PATH\""
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
Where `SCRIPT_PATH` is the resolved absolute path from Step 1. Use forward slashes even on Windows.
|
|
91
|
+
4. Write the updated JSON back (preserve all other settings, use 2-space indentation)
|
|
92
|
+
|
|
93
|
+
### Step 4: Verify and confirm
|
|
94
|
+
|
|
95
|
+
Display:
|
|
96
|
+
```
|
|
97
|
+
✓ PBR status line installed
|
|
98
|
+
|
|
99
|
+
Script: {SCRIPT_PATH}
|
|
100
|
+
Config: ~/.claude/settings.json
|
|
101
|
+
|
|
102
|
+
The status line will appear on your next Claude Code session.
|
|
103
|
+
Restart Claude Code or run `/clear` to activate it now.
|
|
104
|
+
|
|
105
|
+
Customize per-project via .planning/config.json:
|
|
106
|
+
"status_line": {
|
|
107
|
+
"sections": ["phase", "plan", "status", "git", "context"],
|
|
108
|
+
"brand_text": "PBR"
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Subcommand: uninstall
|
|
115
|
+
|
|
116
|
+
1. Read `~/.claude/settings.json`
|
|
117
|
+
2. If no `statusLine` key: inform user "No status line configured." and stop
|
|
118
|
+
3. Remove the `statusLine` key from the JSON
|
|
119
|
+
4. Write the updated file
|
|
120
|
+
5. Display: `✓ PBR status line removed. Restart Claude Code to take effect.`
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Subcommand: preview
|
|
125
|
+
|
|
126
|
+
1. Locate and run the status-line script: `node {SCRIPT_PATH}`
|
|
127
|
+
- Pass sample stdin JSON: `{"context_window": {"used_percentage": 35}, "model": {"display_name": "Claude Opus 4.6"}, "cost": {"total_cost_usd": 0.42}}`
|
|
128
|
+
2. Display the raw output to the user
|
|
129
|
+
3. Also show a description of each section:
|
|
130
|
+
- **Phase**: Current phase number and name from STATE.md
|
|
131
|
+
- **Plan**: Plan progress (N of M)
|
|
132
|
+
- **Status**: Phase status keyword (planning, building, built, etc.)
|
|
133
|
+
- **Git**: Current branch + dirty indicator
|
|
134
|
+
- **Context**: Unicode bar showing context window usage (green/yellow/red)
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Edge Cases
|
|
139
|
+
|
|
140
|
+
### No .planning/ directory
|
|
141
|
+
The status line works even without `.planning/` — it will show only git and context sections. Installation doesn't require a PBR project.
|
|
142
|
+
|
|
143
|
+
### Plugin installed from npm vs local
|
|
144
|
+
The script path differs between `~/.claude/plugins/cache/plan-build-run/pbr/{version}/scripts/status-line.js` and a local `plugins/pbr/scripts/status-line.js`. The install command must resolve the actual path at install time.
|
|
145
|
+
|
|
146
|
+
### Existing non-PBR status line
|
|
147
|
+
If `statusLine` already exists with a different command, warn the user and confirm before replacing.
|