@sienklogic/plan-build-run 2.6.0 → 2.7.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 CHANGED
@@ -5,6 +5,20 @@ 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.7.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.6.0...plan-build-run-v2.7.0) (2026-02-19)
9
+
10
+
11
+ ### Features
12
+
13
+ * **02-01:** add milestone, explore, import, scan write guards to checkSkillRules ([bd21366](https://github.com/SienkLogic/plan-build-run/commit/bd21366f8f63277566035f0827e3fde2ebc39400))
14
+ * **02-02:** add review planner gate to validate-task.js ([89ffb05](https://github.com/SienkLogic/plan-build-run/commit/89ffb05bc6384fc47fbf85ac7c875e16a29db0b9))
15
+ * **02-02:** strengthen ROADMAP sync warnings to CRITICAL level ([7120d60](https://github.com/SienkLogic/plan-build-run/commit/7120d60fdc6678d8c9853679b0d3464116821097))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **tools:** auto-route quick skill to plan skill when user selects Full plan ([252a35e](https://github.com/SienkLogic/plan-build-run/commit/252a35ed9942c2b1902f38923bb80d92d819ae4e))
21
+
8
22
  ## [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
23
 
10
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sienklogic/plan-build-run",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "Plan it, Build it, Run it — structured development workflow for Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pbr",
3
3
  "displayName": "Plan-Build-Run",
4
- "version": "2.6.0",
4
+ "version": "2.7.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",
@@ -76,7 +76,7 @@ Read all relevant context files. This context is used for conflict detection in
76
76
  - trigger equals the phase number as integer
77
77
  - trigger equals * (always matches)
78
78
  9. Pending todos — scan .planning/todos/pending/ for items related to this phase
79
- 10. NOTES.md (if exists at .planning/NOTES.md) — check for related notes
79
+ 10. Notes (if .planning/notes/ exists) — check for related notes
80
80
  ```
81
81
 
82
82
  Collect all of this into a context bundle for use in Steps 4 and 5.
@@ -150,7 +150,7 @@ Run each of these checks. If any matches, record a `[WARNING]`:
150
150
  #### INFO checks (supplementary context):
151
151
  Run each of these checks. If any matches, record an `[INFO]`:
152
152
 
153
- 1. **Related notes**: Are there related notes in NOTES.md?
153
+ 1. **Related notes**: Are there related notes in `.planning/notes/`?
154
154
  2. **Matching seeds**: Are there matching seeds in `.planning/seeds/` that could enhance the plan?
155
155
  3. **Prior phase patterns**: What patterns from prior phases (from SUMMARY.md `patterns` fields) should the imported plan follow?
156
156
 
@@ -23,12 +23,23 @@ This skill runs **inline** — no Task, no AskUserQuestion, no Bash.
23
23
 
24
24
  ---
25
25
 
26
- ## Scope Detection
26
+ ## Storage Format
27
27
 
28
- Two scopes exist. Auto-detect which to use:
28
+ Notes are stored as **individual markdown files** in a notes directory:
29
29
 
30
- 1. **Project scope**: `.planning/NOTES.md` — used when `.planning/` directory exists in cwd
31
- 2. **Global scope**: `~/.claude/notes.md` — used as fallback when no `.planning/`, or when `--global` flag is present
30
+ - **Project scope**: `.planning/notes/{YYYY-MM-DD}-{slug}.md` — used when `.planning/` directory exists in cwd
31
+ - **Global scope**: `~/.claude/notes/{YYYY-MM-DD}-{slug}.md` — used as fallback when no `.planning/`, or when `--global` flag is present
32
+
33
+ Each note file has this format:
34
+
35
+ ```markdown
36
+ ---
37
+ date: "YYYY-MM-DD HH:mm"
38
+ promoted: false
39
+ ---
40
+
41
+ {note text verbatim}
42
+ ```
32
43
 
33
44
  **`--global` flag**: Strip `--global` from anywhere in `$ARGUMENTS` before parsing. When present, force global scope regardless of whether `.planning/` exists.
34
45
 
@@ -53,27 +64,17 @@ Parse `$ARGUMENTS` after stripping `--global`:
53
64
 
54
65
  ## Subcommand: append
55
66
 
56
- Append a timestamped note to the target file.
67
+ Create a timestamped note file in the target directory.
57
68
 
58
69
  ### Steps
59
70
 
60
- 1. Determine scope (project or global) per Scope Detection above
61
- 2. Read the target file if it exists
62
- 3. If the file doesn't exist, create it with this header:
63
-
64
- ```markdown
65
- # Notes
66
-
67
- Quick captures from `/pbr:note`. Ideas worth remembering.
68
-
69
- ---
70
-
71
- ```
72
-
73
- 4. Ensure the file content ends with a newline before appending
74
- 5. Append: `- [YYYY-MM-DD HH:mm] {note text verbatim}`
75
- 6. Write the file
76
- 7. Confirm with exactly one line: `Noted ({scope}): {note text}`
71
+ 1. Determine scope (project or global) per Storage Format above
72
+ 2. Ensure the notes directory exists (`.planning/notes/` or `~/.claude/notes/`)
73
+ 3. Generate slug: first ~4 meaningful words of the note text, lowercase, hyphen-separated (strip articles/prepositions from the start)
74
+ 4. Generate filename: `{YYYY-MM-DD}-{slug}.md`
75
+ - If a file with that name already exists, append `-2`, `-3`, etc.
76
+ 5. Write the file with frontmatter and note text (see Storage Format)
77
+ 6. Confirm with exactly one line: `Noted ({scope}): {note text}`
77
78
  - Where `{scope}` is "project" or "global"
78
79
 
79
80
  ### Constraints
@@ -90,11 +91,11 @@ Show notes from both project and global scopes.
90
91
 
91
92
  ### Steps
92
93
 
93
- 1. Read `.planning/NOTES.md` (if exists) — these are "project" notes
94
- 2. Read `~/.claude/notes.md` (if exists) — these are "global" notes
95
- 3. Parse entries: lines matching `^- \[` are notes
96
- 4. Exclude lines containing `[promoted]` from active counts (but still show them, dimmed)
97
- 5. Number all active entries sequentially starting at 1, using plain integers (1, 2, 3...) for display (across both scopes)
94
+ 1. Glob `.planning/notes/*.md` (if directory exists) — these are "project" notes
95
+ 2. Glob `~/.claude/notes/*.md` (if directory exists) — these are "global" notes
96
+ 3. For each file, read frontmatter to get `date` and `promoted` status
97
+ 4. Exclude files where `promoted: true` from active counts (but still show them, dimmed)
98
+ 5. Sort by date, number all active entries sequentially starting at 1
98
99
  6. If total active entries > 20, show only the last 10 with a note about how many were omitted
99
100
 
100
101
  ### Display Format
@@ -102,18 +103,18 @@ Show notes from both project and global scopes.
102
103
  ```
103
104
  Notes:
104
105
 
105
- Project (.planning/NOTES.md):
106
+ Project (.planning/notes/):
106
107
  1. [2026-02-08 14:32] refactor the hook system to support async validators
107
108
  2. [promoted] [2026-02-08 14:40] add rate limiting to the API endpoints
108
109
  3. [2026-02-08 15:10] consider adding a --dry-run flag to build
109
110
 
110
- Global (~/.claude/notes.md):
111
+ Global (~/.claude/notes/):
111
112
  4. [2026-02-08 10:00] cross-project idea about shared config
112
113
 
113
114
  {count} active note(s). Use `/pbr:note promote <N>` to convert to a todo.
114
115
  ```
115
116
 
116
- If a scope has no file or no entries, show: `(no notes)`
117
+ If a scope has no directory or no entries, show: `(no notes)`
117
118
 
118
119
  ---
119
120
 
@@ -129,7 +130,7 @@ Convert a note into a todo file.
129
130
  4. **Requires `.planning/` directory** — if it doesn't exist, warn: "Todos require a Plan-Build-Run project. Run `/pbr:begin` to initialize one, or use `/pbr:todo add` in an existing project."
130
131
  5. Ensure `.planning/todos/pending/` directory exists
131
132
  6. Generate todo ID: `{NNN}-{slug}` where NNN is the next sequential number (scan both `.planning/todos/pending/` and `.planning/todos/done/` for the highest existing number, increment by 1, zero-pad to 3 digits) and slug is the first ~4 meaningful words of the note text, lowercase, hyphen-separated
132
- 7. Extract the note text (everything after the timestamp)
133
+ 7. Extract the note text from the source file (body after frontmatter)
133
134
  8. Create `.planning/todos/pending/{id}.md`:
134
135
 
135
136
  ```yaml
@@ -155,34 +156,18 @@ Promoted from quick note captured on {original date}.
155
156
  - [ ] {primary criterion derived from note text}
156
157
  ```
157
158
 
158
- 9. Mark the original note as promoted: replace `- [` with `- [promoted] [` on that line
159
+ 9. Mark the source note file as promoted: update its frontmatter to `promoted: true`
159
160
  10. Confirm: `Promoted note {N} to todo {id}: {note text}`
160
161
 
161
162
  ---
162
163
 
163
- ## NOTES.md Format Reference
164
-
165
- ```markdown
166
- # Notes
167
-
168
- Quick captures from `/pbr:note`. Ideas worth remembering.
169
-
170
- ---
171
-
172
- - [2026-02-08 14:32] refactor the hook system to support async validators
173
- - [promoted] [2026-02-08 14:40] add rate limiting to the API endpoints
174
- - [2026-02-08 15:10] consider adding a --dry-run flag to build
175
- ```
176
-
177
- ---
178
-
179
164
  ## Edge Cases
180
165
 
181
166
  1. **"list" as note text**: `/pbr:note list of things` → saves note "list of things" (subcommand only when `list` is the entire arg)
182
- 2. **No `.planning/`**: Falls back to global `~/.claude/notes.md` — works in any directory
167
+ 2. **No `.planning/`**: Falls back to global `~/.claude/notes/` — works in any directory
183
168
  3. **Promote without project**: Warns that todos require `.planning/`, suggests `/pbr:begin`
184
169
  4. **Large files**: `list` shows last 10 when >20 active entries
185
- 5. **Missing newline**: Always ensure trailing newline before appending
170
+ 5. **Duplicate slugs**: Append `-2`, `-3` etc. to filename if slug already used on same date
186
171
  6. **`--global` position**: Stripped from anywhere — `--global my idea` and `my idea --global` both save "my idea" globally
187
172
  7. **Promote already-promoted**: Tell user "Note {N} is already promoted" and stop
188
173
  8. **Empty note text after stripping flags**: Treat as `list` subcommand
@@ -225,3 +210,4 @@ Note {N} not found. Valid range: 1-{max}.
225
210
  4. **DO NOT** create `.planning/` if it doesn't exist — fall back to global
226
211
  5. **DO NOT** number promoted notes in the active count (but still display them)
227
212
  6. **DO NOT** over-format the confirmation — one line is enough
213
+ 7. **DO NOT** use a flat NOTES.md file — always use individual files in notes directory
@@ -85,7 +85,7 @@ Use AskUserQuestion:
85
85
  multiSelect: false
86
86
 
87
87
  If user selects "Quick task": continue to Step 4.
88
- If user selects "Full plan": respond "Use `/pbr:plan` to create a full planning cycle for this task." and stop.
88
+ If user selects "Full plan": clean up `.active-skill` if it exists, then chain directly to the plan skill. The user's task description carries over in conversation context — the plan skill will pick it up.
89
89
  If user selects "Revise": go back to Step 2 to get a new task description.
90
90
  If user types something else (freeform): interpret their response and proceed accordingly.
91
91
 
@@ -32,7 +32,7 @@ Task({
32
32
  - .planning/CONTEXT.md
33
33
  - Any .planning/phases/*/CONTEXT.md files
34
34
  - .planning/research/SUMMARY.md (if exists)
35
- - .planning/NOTES.md (if exists)
35
+ - .planning/notes/*.md (if notes directory exists — read frontmatter for date/promoted status)
36
36
  - .planning/HISTORY.md (if exists — scan for decisions relevant to current work only, do NOT summarize all history)
37
37
 
38
38
  Return ONLY the briefing text. No preamble, no suggestions."
@@ -142,9 +142,9 @@ If any discrepancy found, add: `Run /pbr:resume to auto-reconcile STATE.md.`
142
142
  - Count and summarize if any exist
143
143
 
144
144
  #### Quick Notes
145
- - Check `.planning/NOTES.md` for active note entries
146
- - Count active notes (lines matching `^- \[` that don't contain `[promoted]`)
147
- - Also check `~/.claude/notes.md` for global notes
145
+ - Check `.planning/notes/` directory for note files (individual `.md` files)
146
+ - Count active notes (files where frontmatter does NOT contain `promoted: true`)
147
+ - Also check `~/.claude/notes/` for global notes
148
148
 
149
149
  #### Quick Tasks
150
150
  - Check `.planning/quick/` for recent quick tasks
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pbr",
3
3
  "displayName": "Plan-Build-Run",
4
- "version": "2.6.0",
4
+ "version": "2.7.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",
@@ -77,7 +77,7 @@ Read all relevant context files. This context is used for conflict detection in
77
77
  - trigger equals the phase number as integer
78
78
  - trigger equals * (always matches)
79
79
  9. Pending todos — scan .planning/todos/pending/ for items related to this phase
80
- 10. NOTES.md (if exists at .planning/NOTES.md) — check for related notes
80
+ 10. Notes (if .planning/notes/ exists) — check for related notes
81
81
  ```
82
82
 
83
83
  Collect all of this into a context bundle for use in Steps 4 and 5.
@@ -151,7 +151,7 @@ Run each of these checks. If any matches, record a `[WARNING]`:
151
151
  #### INFO checks (supplementary context):
152
152
  Run each of these checks. If any matches, record an `[INFO]`:
153
153
 
154
- 1. **Related notes**: Are there related notes in NOTES.md?
154
+ 1. **Related notes**: Are there related notes in `.planning/notes/`?
155
155
  2. **Matching seeds**: Are there matching seeds in `.planning/seeds/` that could enhance the plan?
156
156
  3. **Prior phase patterns**: What patterns from prior phases (from SUMMARY.md `patterns` fields) should the imported plan follow?
157
157
 
@@ -24,12 +24,23 @@ This skill runs **inline** — no Task, no AskUserQuestion, no Bash.
24
24
 
25
25
  ---
26
26
 
27
- ## Scope Detection
27
+ ## Storage Format
28
28
 
29
- Two scopes exist. Auto-detect which to use:
29
+ Notes are stored as **individual markdown files** in a notes directory:
30
30
 
31
- 1. **Project scope**: `.planning/NOTES.md` — used when `.planning/` directory exists in cwd
32
- 2. **Global scope**: `~/.claude/notes.md` — used as fallback when no `.planning/`, or when `--global` flag is present
31
+ - **Project scope**: `.planning/notes/{YYYY-MM-DD}-{slug}.md` — used when `.planning/` directory exists in cwd
32
+ - **Global scope**: `~/.claude/notes/{YYYY-MM-DD}-{slug}.md` — used as fallback when no `.planning/`, or when `--global` flag is present
33
+
34
+ Each note file has this format:
35
+
36
+ ```markdown
37
+ ---
38
+ date: "YYYY-MM-DD HH:mm"
39
+ promoted: false
40
+ ---
41
+
42
+ {note text verbatim}
43
+ ```
33
44
 
34
45
  **`--global` flag**: Strip `--global` from anywhere in `$ARGUMENTS` before parsing. When present, force global scope regardless of whether `.planning/` exists.
35
46
 
@@ -54,27 +65,17 @@ Parse `$ARGUMENTS` after stripping `--global`:
54
65
 
55
66
  ## Subcommand: append
56
67
 
57
- Append a timestamped note to the target file.
68
+ Create a timestamped note file in the target directory.
58
69
 
59
70
  ### Steps
60
71
 
61
- 1. Determine scope (project or global) per Scope Detection above
62
- 2. Read the target file if it exists
63
- 3. If the file doesn't exist, create it with this header:
64
-
65
- ```markdown
66
- # Notes
67
-
68
- Quick captures from `/pbr:note`. Ideas worth remembering.
69
-
70
- ---
71
-
72
- ```
73
-
74
- 4. Ensure the file content ends with a newline before appending
75
- 5. Append: `- [YYYY-MM-DD HH:mm] {note text verbatim}`
76
- 6. Write the file
77
- 7. Confirm with exactly one line: `Noted ({scope}): {note text}`
72
+ 1. Determine scope (project or global) per Storage Format above
73
+ 2. Ensure the notes directory exists (`.planning/notes/` or `~/.claude/notes/`)
74
+ 3. Generate slug: first ~4 meaningful words of the note text, lowercase, hyphen-separated (strip articles/prepositions from the start)
75
+ 4. Generate filename: `{YYYY-MM-DD}-{slug}.md`
76
+ - If a file with that name already exists, append `-2`, `-3`, etc.
77
+ 5. Write the file with frontmatter and note text (see Storage Format)
78
+ 6. Confirm with exactly one line: `Noted ({scope}): {note text}`
78
79
  - Where `{scope}` is "project" or "global"
79
80
 
80
81
  ### Constraints
@@ -91,11 +92,11 @@ Show notes from both project and global scopes.
91
92
 
92
93
  ### Steps
93
94
 
94
- 1. Read `.planning/NOTES.md` (if exists) — these are "project" notes
95
- 2. Read `~/.claude/notes.md` (if exists) — these are "global" notes
96
- 3. Parse entries: lines matching `^- \[` are notes
97
- 4. Exclude lines containing `[promoted]` from active counts (but still show them, dimmed)
98
- 5. Number all active entries sequentially starting at 1, using plain integers (1, 2, 3...) for display (across both scopes)
95
+ 1. Glob `.planning/notes/*.md` (if directory exists) — these are "project" notes
96
+ 2. Glob `~/.claude/notes/*.md` (if directory exists) — these are "global" notes
97
+ 3. For each file, read frontmatter to get `date` and `promoted` status
98
+ 4. Exclude files where `promoted: true` from active counts (but still show them, dimmed)
99
+ 5. Sort by date, number all active entries sequentially starting at 1
99
100
  6. If total active entries > 20, show only the last 10 with a note about how many were omitted
100
101
 
101
102
  ### Display Format
@@ -103,18 +104,18 @@ Show notes from both project and global scopes.
103
104
  ```
104
105
  Notes:
105
106
 
106
- Project (.planning/NOTES.md):
107
+ Project (.planning/notes/):
107
108
  1. [2026-02-08 14:32] refactor the hook system to support async validators
108
109
  2. [promoted] [2026-02-08 14:40] add rate limiting to the API endpoints
109
110
  3. [2026-02-08 15:10] consider adding a --dry-run flag to build
110
111
 
111
- Global (~/.claude/notes.md):
112
+ Global (~/.claude/notes/):
112
113
  4. [2026-02-08 10:00] cross-project idea about shared config
113
114
 
114
115
  {count} active note(s). Use `/pbr:note promote <N>` to convert to a todo.
115
116
  ```
116
117
 
117
- If a scope has no file or no entries, show: `(no notes)`
118
+ If a scope has no directory or no entries, show: `(no notes)`
118
119
 
119
120
  ---
120
121
 
@@ -130,7 +131,7 @@ Convert a note into a todo file.
130
131
  4. **Requires `.planning/` directory** — if it doesn't exist, warn: "Todos require a Plan-Build-Run project. Run `/pbr:begin` to initialize one, or use `/pbr:todo add` in an existing project."
131
132
  5. Ensure `.planning/todos/pending/` directory exists
132
133
  6. Generate todo ID: `{NNN}-{slug}` where NNN is the next sequential number (scan both `.planning/todos/pending/` and `.planning/todos/done/` for the highest existing number, increment by 1, zero-pad to 3 digits) and slug is the first ~4 meaningful words of the note text, lowercase, hyphen-separated
133
- 7. Extract the note text (everything after the timestamp)
134
+ 7. Extract the note text from the source file (body after frontmatter)
134
135
  8. Create `.planning/todos/pending/{id}.md`:
135
136
 
136
137
  ```yaml
@@ -156,34 +157,18 @@ Promoted from quick note captured on {original date}.
156
157
  - [ ] {primary criterion derived from note text}
157
158
  ```
158
159
 
159
- 9. Mark the original note as promoted: replace `- [` with `- [promoted] [` on that line
160
+ 9. Mark the source note file as promoted: update its frontmatter to `promoted: true`
160
161
  10. Confirm: `Promoted note {N} to todo {id}: {note text}`
161
162
 
162
163
  ---
163
164
 
164
- ## NOTES.md Format Reference
165
-
166
- ```markdown
167
- # Notes
168
-
169
- Quick captures from `/pbr:note`. Ideas worth remembering.
170
-
171
- ---
172
-
173
- - [2026-02-08 14:32] refactor the hook system to support async validators
174
- - [promoted] [2026-02-08 14:40] add rate limiting to the API endpoints
175
- - [2026-02-08 15:10] consider adding a --dry-run flag to build
176
- ```
177
-
178
- ---
179
-
180
165
  ## Edge Cases
181
166
 
182
167
  1. **"list" as note text**: `/pbr:note list of things` → saves note "list of things" (subcommand only when `list` is the entire arg)
183
- 2. **No `.planning/`**: Falls back to global `~/.claude/notes.md` — works in any directory
168
+ 2. **No `.planning/`**: Falls back to global `~/.claude/notes/` — works in any directory
184
169
  3. **Promote without project**: Warns that todos require `.planning/`, suggests `/pbr:begin`
185
170
  4. **Large files**: `list` shows last 10 when >20 active entries
186
- 5. **Missing newline**: Always ensure trailing newline before appending
171
+ 5. **Duplicate slugs**: Append `-2`, `-3` etc. to filename if slug already used on same date
187
172
  6. **`--global` position**: Stripped from anywhere — `--global my idea` and `my idea --global` both save "my idea" globally
188
173
  7. **Promote already-promoted**: Tell user "Note {N} is already promoted" and stop
189
174
  8. **Empty note text after stripping flags**: Treat as `list` subcommand
@@ -226,3 +211,4 @@ Note {N} not found. Valid range: 1-{max}.
226
211
  4. **DO NOT** create `.planning/` if it doesn't exist — fall back to global
227
212
  5. **DO NOT** number promoted notes in the active count (but still display them)
228
213
  6. **DO NOT** over-format the confirmation — one line is enough
214
+ 7. **DO NOT** use a flat NOTES.md file — always use individual files in notes directory
@@ -85,7 +85,7 @@ Use AskUserQuestion:
85
85
  multiSelect: false
86
86
 
87
87
  If user selects "Quick task": continue to Step 4.
88
- If user selects "Full plan": respond "Use `/pbr:plan` to create a full planning cycle for this task." and stop.
88
+ If user selects "Full plan": clean up `.active-skill` if it exists, then chain directly to the plan skill. The user's task description carries over in conversation context — the plan skill will pick it up.
89
89
  If user selects "Revise": go back to Step 2 to get a new task description.
90
90
  If user types something else (freeform): interpret their response and proceed accordingly.
91
91
 
@@ -32,7 +32,7 @@ Task({
32
32
  - .planning/CONTEXT.md
33
33
  - Any .planning/phases/*/CONTEXT.md files
34
34
  - .planning/research/SUMMARY.md (if exists)
35
- - .planning/NOTES.md (if exists)
35
+ - .planning/notes/*.md (if notes directory exists — read frontmatter for date/promoted status)
36
36
  - .planning/HISTORY.md (if exists — scan for decisions relevant to current work only, do NOT summarize all history)
37
37
 
38
38
  Return ONLY the briefing text. No preamble, no suggestions."
@@ -142,9 +142,9 @@ If any discrepancy found, add: `Run /pbr:resume to auto-reconcile STATE.md.`
142
142
  - Count and summarize if any exist
143
143
 
144
144
  #### Quick Notes
145
- - Check `.planning/NOTES.md` for active note entries
146
- - Count active notes (lines matching `^- \[` that don't contain `[promoted]`)
147
- - Also check `~/.claude/notes.md` for global notes
145
+ - Check `.planning/notes/` directory for note files (individual `.md` files)
146
+ - Count active notes (files where frontmatter does NOT contain `promoted: true`)
147
+ - Also check `~/.claude/notes/` for global notes
148
148
 
149
149
  #### Quick Tasks
150
150
  - Check `.planning/quick/` for recent quick tasks
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pbr",
3
- "version": "2.6.0",
3
+ "version": "2.7.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",
@@ -60,9 +60,18 @@ function main() {
60
60
 
61
61
  const roadmapStatus = getRoadmapPhaseStatus(roadmapContent, stateInfo.phase);
62
62
  if (!roadmapStatus) {
63
- logHook('check-roadmap-sync', 'PostToolUse', 'skip', {
63
+ logHook('check-roadmap-sync', 'PostToolUse', 'warn', {
64
64
  reason: `phase ${stateInfo.phase} not found in ROADMAP.md table`
65
65
  });
66
+ logEvent('workflow', 'roadmap-sync', {
67
+ phase: stateInfo.phase,
68
+ stateStatus: stateInfo.status,
69
+ status: 'missing-phase'
70
+ });
71
+ const output = {
72
+ additionalContext: `CRITICAL: Phase ${stateInfo.phase} has status "${stateInfo.status}" in STATE.md but is not listed in ROADMAP.md Progress table. Update the ROADMAP.md Progress table NOW before continuing. Run: \`node plugins/pbr/scripts/pbr-tools.js state load\` to check current state.`
73
+ };
74
+ process.stdout.write(JSON.stringify(output));
66
75
  process.exit(0);
67
76
  }
68
77
 
@@ -80,7 +89,7 @@ function main() {
80
89
  });
81
90
 
82
91
  const output = {
83
- additionalContext: `ROADMAP.md out of sync: Phase ${stateInfo.phase} is "${roadmapStatus}" in ROADMAP.md but "${stateInfo.status}" in STATE.md. Update the Phase Overview table in ROADMAP.md to match.`
92
+ additionalContext: `CRITICAL: ROADMAP.md out of sync Phase ${stateInfo.phase} is "${roadmapStatus}" in ROADMAP.md but "${stateInfo.status}" in STATE.md. Update the ROADMAP.md Progress table NOW before continuing. Run: \`node plugins/pbr/scripts/pbr-tools.js state load\` to check current state.`
84
93
  };
85
94
  process.stdout.write(JSON.stringify(output));
86
95
  } else {
@@ -223,10 +232,17 @@ function checkSync(data) {
223
232
 
224
233
  const roadmapStatus = getRoadmapPhaseStatus(roadmapContent, stateInfo.phase);
225
234
  if (!roadmapStatus) {
226
- logHook('check-roadmap-sync', 'PostToolUse', 'skip', {
235
+ logHook('check-roadmap-sync', 'PostToolUse', 'warn', {
227
236
  reason: `phase ${stateInfo.phase} not found in ROADMAP.md table`
228
237
  });
229
- return null;
238
+ logEvent('workflow', 'roadmap-sync', {
239
+ phase: stateInfo.phase, stateStatus: stateInfo.status, status: 'missing-phase'
240
+ });
241
+ return {
242
+ output: {
243
+ additionalContext: `CRITICAL: Phase ${stateInfo.phase} has status "${stateInfo.status}" in STATE.md but is not listed in ROADMAP.md Progress table. Update the ROADMAP.md Progress table NOW before continuing. Run: \`node plugins/pbr/scripts/pbr-tools.js state load\` to check current state.`
244
+ }
245
+ };
230
246
  }
231
247
 
232
248
  if (roadmapStatus.toLowerCase() !== stateInfo.status) {
@@ -238,7 +254,7 @@ function checkSync(data) {
238
254
  });
239
255
  return {
240
256
  output: {
241
- additionalContext: `ROADMAP.md out of sync: Phase ${stateInfo.phase} is "${roadmapStatus}" in ROADMAP.md but "${stateInfo.status}" in STATE.md. Update the Phase Overview table in ROADMAP.md to match.`
257
+ additionalContext: `CRITICAL: ROADMAP.md out of sync Phase ${stateInfo.phase} is "${roadmapStatus}" in ROADMAP.md but "${stateInfo.status}" in STATE.md. Update the ROADMAP.md Progress table NOW before continuing. Run: \`node plugins/pbr/scripts/pbr-tools.js state load\` to check current state.`
242
258
  }
243
259
  };
244
260
  }
@@ -10,6 +10,8 @@
10
10
  * - /pbr:quick: Cannot write files outside .planning/ until a PLAN.md
11
11
  * exists in .planning/quick/. This prevents the orchestrator from
12
12
  * skipping the planning steps and jumping straight to implementation.
13
+ * - /pbr:milestone, /pbr:explore, /pbr:import, /pbr:scan: Read-only skills
14
+ * that cannot write files outside .planning/.
13
15
  *
14
16
  * Skills opt in by writing .planning/.active-skill at the start of
15
17
  * their execution. If the file doesn't exist, this hook does nothing.
@@ -116,9 +118,14 @@ function checkSkillRules(skill, filePath, planningDir) {
116
118
  return checkBuildRules(filePath, isInPlanning, planningDir);
117
119
  case 'statusline':
118
120
  return checkStatuslineRules(filePath, isInPlanning, planningDir);
121
+ case 'plan':
119
122
  case 'review':
120
123
  case 'discuss':
121
124
  case 'begin':
125
+ case 'milestone':
126
+ case 'explore':
127
+ case 'import':
128
+ case 'scan':
122
129
  return checkReadOnlySkillRules(skill, filePath, isInPlanning);
123
130
  default:
124
131
  return null;
@@ -125,10 +125,10 @@ function buildContext(planningDir, stateFile) {
125
125
  }
126
126
 
127
127
  // Check for quick notes
128
- const projectNotesFile = path.join(planningDir, 'NOTES.md');
129
- const globalNotesFile = path.join(os.homedir(), '.claude', 'notes.md');
130
- const projectNoteCount = countNotes(projectNotesFile);
131
- const globalNoteCount = countNotes(globalNotesFile);
128
+ const projectNotesDir = path.join(planningDir, 'notes');
129
+ const globalNotesDir = path.join(os.homedir(), '.claude', 'notes');
130
+ const projectNoteCount = countNotes(projectNotesDir);
131
+ const globalNoteCount = countNotes(globalNotesDir);
132
132
  if (projectNoteCount > 0 || globalNoteCount > 0) {
133
133
  const noteParts = [];
134
134
  if (projectNoteCount > 0) noteParts.push(`${projectNoteCount} project`);
@@ -226,12 +226,16 @@ function findContinueFiles(dir) {
226
226
  return results;
227
227
  }
228
228
 
229
- function countNotes(filePath) {
229
+ function countNotes(notesDir) {
230
230
  try {
231
- if (!fs.existsSync(filePath)) return 0;
232
- const content = fs.readFileSync(filePath, 'utf8');
233
- const lines = content.split('\n');
234
- return lines.filter(l => /^- \[/.test(l) && !l.includes('[promoted]')).length;
231
+ if (!fs.existsSync(notesDir)) return 0;
232
+ const files = fs.readdirSync(notesDir).filter(f => f.endsWith('.md'));
233
+ let count = 0;
234
+ for (const file of files) {
235
+ const content = fs.readFileSync(path.join(notesDir, file), 'utf8');
236
+ if (!content.includes('promoted: true')) count++;
237
+ }
238
+ return count;
235
239
  } catch (_e) {
236
240
  return 0;
237
241
  }
@@ -252,6 +252,60 @@ function checkPlanExecutorGate(data) {
252
252
  };
253
253
  }
254
254
 
255
+ /**
256
+ * Blocking check: when the active skill is "review" and a planner is being
257
+ * spawned, verify that a VERIFICATION.md exists in the current phase directory.
258
+ * Returns { block: true, reason: "..." } if blocked, or null if OK.
259
+ */
260
+ function checkReviewPlannerGate(data) {
261
+ const toolInput = data.tool_input || {};
262
+ const subagentType = toolInput.subagent_type || '';
263
+
264
+ // Only gate pbr:planner
265
+ if (subagentType !== 'pbr:planner') return null;
266
+
267
+ const cwd = process.cwd();
268
+ const planningDir = path.join(cwd, '.planning');
269
+ const activeSkillFile = path.join(planningDir, '.active-skill');
270
+
271
+ // Only gate when active skill is "review"
272
+ try {
273
+ const activeSkill = fs.readFileSync(activeSkillFile, 'utf8').trim();
274
+ if (activeSkill !== 'review') return null;
275
+ } catch (_e) {
276
+ return null;
277
+ }
278
+
279
+ // Read STATE.md for current phase
280
+ const stateFile = path.join(planningDir, 'STATE.md');
281
+ try {
282
+ const state = fs.readFileSync(stateFile, 'utf8');
283
+ const phaseMatch = state.match(/Phase:\s*(\d+)/);
284
+ if (!phaseMatch) return null;
285
+
286
+ const currentPhase = phaseMatch[1].padStart(2, '0');
287
+ const phasesDir = path.join(planningDir, 'phases');
288
+ if (!fs.existsSync(phasesDir)) return null;
289
+
290
+ const dirs = fs.readdirSync(phasesDir).filter(d => d.startsWith(currentPhase + '-'));
291
+ if (dirs.length === 0) return null;
292
+
293
+ const phaseDir = path.join(phasesDir, dirs[0]);
294
+ const hasVerification = fs.existsSync(path.join(phaseDir, 'VERIFICATION.md'));
295
+
296
+ if (!hasVerification) {
297
+ return {
298
+ block: true,
299
+ reason: 'Review planner gate: Cannot spawn planner for gap closure without a VERIFICATION.md. Run /pbr:review first to generate verification results.'
300
+ };
301
+ }
302
+ } catch (_e) {
303
+ return null;
304
+ }
305
+
306
+ return null;
307
+ }
308
+
255
309
  function main() {
256
310
  let input = '';
257
311
 
@@ -285,6 +339,18 @@ function main() {
285
339
  return;
286
340
  }
287
341
 
342
+ // Blocking gate: review skill planner needs VERIFICATION.md
343
+ const reviewGate = checkReviewPlannerGate(data);
344
+ if (reviewGate && reviewGate.block) {
345
+ logHook('validate-task', 'PreToolUse', 'blocked', { reason: reviewGate.reason });
346
+ process.stdout.write(JSON.stringify({
347
+ decision: 'block',
348
+ reason: reviewGate.reason
349
+ }));
350
+ process.exit(2);
351
+ return;
352
+ }
353
+
288
354
  // Blocking gate: plan skill cannot spawn executors
289
355
  const planGate = checkPlanExecutorGate(data);
290
356
  if (planGate && planGate.block) {
@@ -317,5 +383,5 @@ function main() {
317
383
  });
318
384
  }
319
385
 
320
- module.exports = { checkTask, checkQuickExecutorGate, checkBuildExecutorGate, checkPlanExecutorGate, KNOWN_AGENTS, MAX_DESCRIPTION_LENGTH };
386
+ module.exports = { checkTask, checkQuickExecutorGate, checkBuildExecutorGate, checkPlanExecutorGate, checkReviewPlannerGate, KNOWN_AGENTS, MAX_DESCRIPTION_LENGTH };
321
387
  if (require.main === module || process.argv[1] === __filename) { main(); }
@@ -80,7 +80,7 @@ Read all relevant context files. This context is used for conflict detection in
80
80
  - trigger equals the phase number as integer
81
81
  - trigger equals * (always matches)
82
82
  9. Pending todos — scan .planning/todos/pending/ for items related to this phase
83
- 10. NOTES.md (if exists at .planning/NOTES.md) — check for related notes
83
+ 10. Notes (if .planning/notes/ exists) — check for related notes
84
84
  ```
85
85
 
86
86
  Collect all of this into a context bundle for use in Steps 4 and 5.
@@ -154,7 +154,7 @@ Run each of these checks. If any matches, record a `[WARNING]`:
154
154
  #### INFO checks (supplementary context):
155
155
  Run each of these checks. If any matches, record an `[INFO]`:
156
156
 
157
- 1. **Related notes**: Are there related notes in NOTES.md?
157
+ 1. **Related notes**: Are there related notes in `.planning/notes/`?
158
158
  2. **Matching seeds**: Are there matching seeds in `.planning/seeds/` that could enhance the plan?
159
159
  3. **Prior phase patterns**: What patterns from prior phases (from SUMMARY.md `patterns` fields) should the imported plan follow?
160
160
 
@@ -27,12 +27,23 @@ This skill runs **inline** — no Task, no AskUserQuestion, no Bash.
27
27
 
28
28
  ---
29
29
 
30
- ## Scope Detection
30
+ ## Storage Format
31
31
 
32
- Two scopes exist. Auto-detect which to use:
32
+ Notes are stored as **individual markdown files** in a notes directory:
33
33
 
34
- 1. **Project scope**: `.planning/NOTES.md` — used when `.planning/` directory exists in cwd
35
- 2. **Global scope**: `~/.claude/notes.md` — used as fallback when no `.planning/`, or when `--global` flag is present
34
+ - **Project scope**: `.planning/notes/{YYYY-MM-DD}-{slug}.md` — used when `.planning/` directory exists in cwd
35
+ - **Global scope**: `~/.claude/notes/{YYYY-MM-DD}-{slug}.md` — used as fallback when no `.planning/`, or when `--global` flag is present
36
+
37
+ Each note file has this format:
38
+
39
+ ```markdown
40
+ ---
41
+ date: "YYYY-MM-DD HH:mm"
42
+ promoted: false
43
+ ---
44
+
45
+ {note text verbatim}
46
+ ```
36
47
 
37
48
  **`--global` flag**: Strip `--global` from anywhere in `$ARGUMENTS` before parsing. When present, force global scope regardless of whether `.planning/` exists.
38
49
 
@@ -57,27 +68,17 @@ Parse `$ARGUMENTS` after stripping `--global`:
57
68
 
58
69
  ## Subcommand: append
59
70
 
60
- Append a timestamped note to the target file.
71
+ Create a timestamped note file in the target directory.
61
72
 
62
73
  ### Steps
63
74
 
64
- 1. Determine scope (project or global) per Scope Detection above
65
- 2. Read the target file if it exists
66
- 3. If the file doesn't exist, create it with this header:
67
-
68
- ```markdown
69
- # Notes
70
-
71
- Quick captures from `/pbr:note`. Ideas worth remembering.
72
-
73
- ---
74
-
75
- ```
76
-
77
- 4. Ensure the file content ends with a newline before appending
78
- 5. Append: `- [YYYY-MM-DD HH:mm] {note text verbatim}`
79
- 6. Write the file
80
- 7. Confirm with exactly one line: `Noted ({scope}): {note text}`
75
+ 1. Determine scope (project or global) per Storage Format above
76
+ 2. Ensure the notes directory exists (`.planning/notes/` or `~/.claude/notes/`)
77
+ 3. Generate slug: first ~4 meaningful words of the note text, lowercase, hyphen-separated (strip articles/prepositions from the start)
78
+ 4. Generate filename: `{YYYY-MM-DD}-{slug}.md`
79
+ - If a file with that name already exists, append `-2`, `-3`, etc.
80
+ 5. Write the file with frontmatter and note text (see Storage Format)
81
+ 6. Confirm with exactly one line: `Noted ({scope}): {note text}`
81
82
  - Where `{scope}` is "project" or "global"
82
83
 
83
84
  ### Constraints
@@ -94,11 +95,11 @@ Show notes from both project and global scopes.
94
95
 
95
96
  ### Steps
96
97
 
97
- 1. Read `.planning/NOTES.md` (if exists) — these are "project" notes
98
- 2. Read `~/.claude/notes.md` (if exists) — these are "global" notes
99
- 3. Parse entries: lines matching `^- \[` are notes
100
- 4. Exclude lines containing `[promoted]` from active counts (but still show them, dimmed)
101
- 5. Number all active entries sequentially starting at 1, using plain integers (1, 2, 3...) for display (across both scopes)
98
+ 1. Glob `.planning/notes/*.md` (if directory exists) — these are "project" notes
99
+ 2. Glob `~/.claude/notes/*.md` (if directory exists) — these are "global" notes
100
+ 3. For each file, read frontmatter to get `date` and `promoted` status
101
+ 4. Exclude files where `promoted: true` from active counts (but still show them, dimmed)
102
+ 5. Sort by date, number all active entries sequentially starting at 1
102
103
  6. If total active entries > 20, show only the last 10 with a note about how many were omitted
103
104
 
104
105
  ### Display Format
@@ -106,18 +107,18 @@ Show notes from both project and global scopes.
106
107
  ```
107
108
  Notes:
108
109
 
109
- Project (.planning/NOTES.md):
110
+ Project (.planning/notes/):
110
111
  1. [2026-02-08 14:32] refactor the hook system to support async validators
111
112
  2. [promoted] [2026-02-08 14:40] add rate limiting to the API endpoints
112
113
  3. [2026-02-08 15:10] consider adding a --dry-run flag to build
113
114
 
114
- Global (~/.claude/notes.md):
115
+ Global (~/.claude/notes/):
115
116
  4. [2026-02-08 10:00] cross-project idea about shared config
116
117
 
117
118
  {count} active note(s). Use `/pbr:note promote <N>` to convert to a todo.
118
119
  ```
119
120
 
120
- If a scope has no file or no entries, show: `(no notes)`
121
+ If a scope has no directory or no entries, show: `(no notes)`
121
122
 
122
123
  ---
123
124
 
@@ -133,7 +134,7 @@ Convert a note into a todo file.
133
134
  4. **Requires `.planning/` directory** — if it doesn't exist, warn: "Todos require a Plan-Build-Run project. Run `/pbr:begin` to initialize one, or use `/pbr:todo add` in an existing project."
134
135
  5. Ensure `.planning/todos/pending/` directory exists
135
136
  6. Generate todo ID: `{NNN}-{slug}` where NNN is the next sequential number (scan both `.planning/todos/pending/` and `.planning/todos/done/` for the highest existing number, increment by 1, zero-pad to 3 digits) and slug is the first ~4 meaningful words of the note text, lowercase, hyphen-separated
136
- 7. Extract the note text (everything after the timestamp)
137
+ 7. Extract the note text from the source file (body after frontmatter)
137
138
  8. Create `.planning/todos/pending/{id}.md`:
138
139
 
139
140
  ```yaml
@@ -159,34 +160,18 @@ Promoted from quick note captured on {original date}.
159
160
  - [ ] {primary criterion derived from note text}
160
161
  ```
161
162
 
162
- 9. Mark the original note as promoted: replace `- [` with `- [promoted] [` on that line
163
+ 9. Mark the source note file as promoted: update its frontmatter to `promoted: true`
163
164
  10. Confirm: `Promoted note {N} to todo {id}: {note text}`
164
165
 
165
166
  ---
166
167
 
167
- ## NOTES.md Format Reference
168
-
169
- ```markdown
170
- # Notes
171
-
172
- Quick captures from `/pbr:note`. Ideas worth remembering.
173
-
174
- ---
175
-
176
- - [2026-02-08 14:32] refactor the hook system to support async validators
177
- - [promoted] [2026-02-08 14:40] add rate limiting to the API endpoints
178
- - [2026-02-08 15:10] consider adding a --dry-run flag to build
179
- ```
180
-
181
- ---
182
-
183
168
  ## Edge Cases
184
169
 
185
170
  1. **"list" as note text**: `/pbr:note list of things` → saves note "list of things" (subcommand only when `list` is the entire arg)
186
- 2. **No `.planning/`**: Falls back to global `~/.claude/notes.md` — works in any directory
171
+ 2. **No `.planning/`**: Falls back to global `~/.claude/notes/` — works in any directory
187
172
  3. **Promote without project**: Warns that todos require `.planning/`, suggests `/pbr:begin`
188
173
  4. **Large files**: `list` shows last 10 when >20 active entries
189
- 5. **Missing newline**: Always ensure trailing newline before appending
174
+ 5. **Duplicate slugs**: Append `-2`, `-3` etc. to filename if slug already used on same date
190
175
  6. **`--global` position**: Stripped from anywhere — `--global my idea` and `my idea --global` both save "my idea" globally
191
176
  7. **Promote already-promoted**: Tell user "Note {N} is already promoted" and stop
192
177
  8. **Empty note text after stripping flags**: Treat as `list` subcommand
@@ -229,3 +214,4 @@ Note {N} not found. Valid range: 1-{max}.
229
214
  4. **DO NOT** create `.planning/` if it doesn't exist — fall back to global
230
215
  5. **DO NOT** number promoted notes in the active count (but still display them)
231
216
  6. **DO NOT** over-format the confirmation — one line is enough
217
+ 7. **DO NOT** use a flat NOTES.md file — always use individual files in notes directory
@@ -86,7 +86,7 @@ Use AskUserQuestion:
86
86
  multiSelect: false
87
87
 
88
88
  If user selects "Quick task": continue to Step 4.
89
- If user selects "Full plan": respond "Use `/pbr:plan` to create a full planning cycle for this task." and stop.
89
+ If user selects "Full plan": clean up `.active-skill` if it exists, then chain directly: `Skill({ skill: "pbr:plan", args: "" })`. The user's task description carries over in conversation context — the plan skill will pick it up.
90
90
  If user selects "Revise": go back to Step 2 to get a new task description.
91
91
  If user types something else (freeform): interpret their response and proceed accordingly.
92
92
 
@@ -31,7 +31,7 @@ Task({
31
31
  - .planning/CONTEXT.md
32
32
  - Any .planning/phases/*/CONTEXT.md files
33
33
  - .planning/research/SUMMARY.md (if exists)
34
- - .planning/NOTES.md (if exists)
34
+ - .planning/notes/*.md (if notes directory exists — read frontmatter for date/promoted status)
35
35
  - .planning/HISTORY.md (if exists — scan for decisions relevant to current work only, do NOT summarize all history)
36
36
 
37
37
  Return ONLY the briefing text. No preamble, no suggestions."
@@ -147,9 +147,9 @@ If any discrepancy found, add: `Run /pbr:resume to auto-reconcile STATE.md.`
147
147
  - Count and summarize if any exist
148
148
 
149
149
  #### Quick Notes
150
- - Check `.planning/NOTES.md` for active note entries
151
- - Count active notes (lines matching `^- \[` that don't contain `[promoted]`)
152
- - Also check `~/.claude/notes.md` for global notes
150
+ - Check `.planning/notes/` directory for note files (individual `.md` files)
151
+ - Count active notes (files where frontmatter does NOT contain `promoted: true`)
152
+ - Also check `~/.claude/notes/` for global notes
153
153
 
154
154
  #### Quick Tasks
155
155
  - Check `.planning/quick/` for recent quick tasks