@sienklogic/plan-build-run 2.34.0 → 2.38.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.
Files changed (160) hide show
  1. package/CHANGELOG.md +683 -0
  2. package/dashboard/public/css/command-center.css +152 -65
  3. package/dashboard/public/css/explorer.css +22 -41
  4. package/dashboard/public/css/layout.css +119 -1
  5. package/dashboard/public/css/tokens.css +13 -0
  6. package/dashboard/src/components/Layout.tsx +32 -6
  7. package/dashboard/src/components/explorer/tabs/PhasesTab.tsx +11 -1
  8. package/dashboard/src/components/explorer/tabs/TodosTab.tsx +18 -2
  9. package/dashboard/src/components/partials/AttentionPanel.tsx +7 -1
  10. package/dashboard/src/components/partials/CurrentPhaseCard.tsx +26 -24
  11. package/dashboard/src/components/partials/QuickActions.tsx +21 -11
  12. package/dashboard/src/components/partials/StatCardGrid.tsx +67 -0
  13. package/dashboard/src/components/partials/StatusHeader.tsx +1 -0
  14. package/dashboard/src/routes/command-center.routes.tsx +8 -7
  15. package/dashboard/src/routes/index.routes.tsx +32 -29
  16. package/package.json +2 -2
  17. package/plugins/copilot-pbr/agents/audit.agent.md +129 -16
  18. package/plugins/copilot-pbr/agents/codebase-mapper.agent.md +49 -1
  19. package/plugins/copilot-pbr/agents/debugger.agent.md +50 -1
  20. package/plugins/copilot-pbr/agents/dev-sync.agent.md +23 -0
  21. package/plugins/copilot-pbr/agents/executor.agent.md +153 -8
  22. package/plugins/copilot-pbr/agents/general.agent.md +46 -1
  23. package/plugins/copilot-pbr/agents/integration-checker.agent.md +55 -2
  24. package/plugins/copilot-pbr/agents/plan-checker.agent.md +50 -2
  25. package/plugins/copilot-pbr/agents/planner.agent.md +80 -1
  26. package/plugins/copilot-pbr/agents/researcher.agent.md +50 -2
  27. package/plugins/copilot-pbr/agents/synthesizer.agent.md +49 -1
  28. package/plugins/copilot-pbr/agents/verifier.agent.md +114 -13
  29. package/plugins/copilot-pbr/commands/test.md +5 -0
  30. package/plugins/copilot-pbr/hooks/hooks.json +11 -0
  31. package/plugins/copilot-pbr/plugin.json +1 -1
  32. package/plugins/copilot-pbr/references/agent-contracts.md +27 -0
  33. package/plugins/copilot-pbr/references/checkpoints.md +32 -1
  34. package/plugins/copilot-pbr/references/context-quality-tiers.md +45 -0
  35. package/plugins/copilot-pbr/references/pbr-tools-cli.md +115 -0
  36. package/plugins/copilot-pbr/references/questioning.md +21 -1
  37. package/plugins/copilot-pbr/references/verification-patterns.md +96 -18
  38. package/plugins/copilot-pbr/skills/audit/SKILL.md +19 -3
  39. package/plugins/copilot-pbr/skills/begin/SKILL.md +57 -4
  40. package/plugins/copilot-pbr/skills/build/SKILL.md +39 -2
  41. package/plugins/copilot-pbr/skills/config/SKILL.md +12 -2
  42. package/plugins/copilot-pbr/skills/debug/SKILL.md +12 -1
  43. package/plugins/copilot-pbr/skills/explore/SKILL.md +13 -2
  44. package/plugins/copilot-pbr/skills/health/SKILL.md +13 -5
  45. package/plugins/copilot-pbr/skills/import/SKILL.md +26 -1
  46. package/plugins/copilot-pbr/skills/milestone/SKILL.md +15 -3
  47. package/plugins/copilot-pbr/skills/plan/SKILL.md +50 -0
  48. package/plugins/copilot-pbr/skills/quick/SKILL.md +21 -0
  49. package/plugins/copilot-pbr/skills/review/SKILL.md +45 -0
  50. package/plugins/copilot-pbr/skills/scan/SKILL.md +20 -0
  51. package/plugins/copilot-pbr/skills/setup/SKILL.md +9 -1
  52. package/plugins/copilot-pbr/skills/shared/context-budget.md +10 -0
  53. package/plugins/copilot-pbr/skills/shared/universal-anti-patterns.md +6 -0
  54. package/plugins/copilot-pbr/skills/test/SKILL.md +210 -0
  55. package/plugins/copilot-pbr/templates/SUMMARY-complex.md.tmpl +95 -0
  56. package/plugins/copilot-pbr/templates/SUMMARY-minimal.md.tmpl +48 -0
  57. package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
  58. package/plugins/cursor-pbr/agents/audit.md +52 -5
  59. package/plugins/cursor-pbr/agents/codebase-mapper.md +49 -1
  60. package/plugins/cursor-pbr/agents/debugger.md +50 -1
  61. package/plugins/cursor-pbr/agents/dev-sync.md +23 -0
  62. package/plugins/cursor-pbr/agents/executor.md +153 -8
  63. package/plugins/cursor-pbr/agents/general.md +46 -1
  64. package/plugins/cursor-pbr/agents/integration-checker.md +54 -1
  65. package/plugins/cursor-pbr/agents/plan-checker.md +49 -1
  66. package/plugins/cursor-pbr/agents/planner.md +80 -1
  67. package/plugins/cursor-pbr/agents/researcher.md +49 -1
  68. package/plugins/cursor-pbr/agents/synthesizer.md +49 -1
  69. package/plugins/cursor-pbr/agents/verifier.md +113 -12
  70. package/plugins/cursor-pbr/commands/test.md +5 -0
  71. package/plugins/cursor-pbr/hooks/hooks.json +9 -0
  72. package/plugins/cursor-pbr/references/agent-contracts.md +27 -0
  73. package/plugins/cursor-pbr/references/checkpoints.md +32 -1
  74. package/plugins/cursor-pbr/references/context-quality-tiers.md +45 -0
  75. package/plugins/cursor-pbr/references/pbr-tools-cli.md +115 -0
  76. package/plugins/cursor-pbr/references/questioning.md +21 -1
  77. package/plugins/cursor-pbr/references/verification-patterns.md +96 -18
  78. package/plugins/cursor-pbr/skills/audit/SKILL.md +19 -3
  79. package/plugins/cursor-pbr/skills/begin/SKILL.md +57 -4
  80. package/plugins/cursor-pbr/skills/build/SKILL.md +37 -2
  81. package/plugins/cursor-pbr/skills/config/SKILL.md +12 -2
  82. package/plugins/cursor-pbr/skills/debug/SKILL.md +12 -1
  83. package/plugins/cursor-pbr/skills/explore/SKILL.md +13 -2
  84. package/plugins/cursor-pbr/skills/health/SKILL.md +14 -5
  85. package/plugins/cursor-pbr/skills/import/SKILL.md +26 -1
  86. package/plugins/cursor-pbr/skills/milestone/SKILL.md +15 -3
  87. package/plugins/cursor-pbr/skills/plan/SKILL.md +50 -0
  88. package/plugins/cursor-pbr/skills/quick/SKILL.md +21 -0
  89. package/plugins/cursor-pbr/skills/review/SKILL.md +45 -0
  90. package/plugins/cursor-pbr/skills/scan/SKILL.md +20 -0
  91. package/plugins/cursor-pbr/skills/setup/SKILL.md +9 -1
  92. package/plugins/cursor-pbr/skills/shared/context-budget.md +10 -0
  93. package/plugins/cursor-pbr/skills/shared/universal-anti-patterns.md +6 -0
  94. package/plugins/cursor-pbr/skills/test/SKILL.md +211 -0
  95. package/plugins/cursor-pbr/templates/SUMMARY-complex.md.tmpl +95 -0
  96. package/plugins/cursor-pbr/templates/SUMMARY-minimal.md.tmpl +48 -0
  97. package/plugins/pbr/.claude-plugin/plugin.json +1 -1
  98. package/plugins/pbr/agents/audit.md +45 -0
  99. package/plugins/pbr/agents/codebase-mapper.md +48 -0
  100. package/plugins/pbr/agents/debugger.md +49 -0
  101. package/plugins/pbr/agents/dev-sync.md +23 -0
  102. package/plugins/pbr/agents/executor.md +151 -6
  103. package/plugins/pbr/agents/general.md +45 -0
  104. package/plugins/pbr/agents/integration-checker.md +53 -0
  105. package/plugins/pbr/agents/plan-checker.md +48 -0
  106. package/plugins/pbr/agents/planner.md +78 -1
  107. package/plugins/pbr/agents/researcher.md +48 -0
  108. package/plugins/pbr/agents/synthesizer.md +48 -0
  109. package/plugins/pbr/agents/verifier.md +112 -11
  110. package/plugins/pbr/commands/test.md +5 -0
  111. package/plugins/pbr/hooks/hooks.json +9 -0
  112. package/plugins/pbr/references/agent-contracts.md +27 -0
  113. package/plugins/pbr/references/checkpoints.md +32 -0
  114. package/plugins/pbr/references/context-quality-tiers.md +45 -0
  115. package/plugins/pbr/references/pbr-tools-cli.md +115 -0
  116. package/plugins/pbr/references/questioning.md +21 -0
  117. package/plugins/pbr/references/verification-patterns.md +96 -17
  118. package/plugins/pbr/scripts/check-plan-format.js +13 -1
  119. package/plugins/pbr/scripts/check-state-sync.js +26 -7
  120. package/plugins/pbr/scripts/check-subagent-output.js +30 -2
  121. package/plugins/pbr/scripts/config-schema.json +11 -1
  122. package/plugins/pbr/scripts/context-bridge.js +265 -0
  123. package/plugins/pbr/scripts/lib/config.js +271 -0
  124. package/plugins/pbr/scripts/lib/core.js +587 -0
  125. package/plugins/pbr/scripts/lib/history.js +73 -0
  126. package/plugins/pbr/scripts/lib/init.js +166 -0
  127. package/plugins/pbr/scripts/lib/migrate.js +169 -0
  128. package/plugins/pbr/scripts/lib/phase.js +364 -0
  129. package/plugins/pbr/scripts/lib/roadmap.js +175 -0
  130. package/plugins/pbr/scripts/lib/state.js +397 -0
  131. package/plugins/pbr/scripts/lib/todo.js +300 -0
  132. package/plugins/pbr/scripts/pbr-tools.js +425 -1310
  133. package/plugins/pbr/scripts/post-write-dispatch.js +5 -4
  134. package/plugins/pbr/scripts/pre-write-dispatch.js +1 -1
  135. package/plugins/pbr/scripts/progress-tracker.js +1 -1
  136. package/plugins/pbr/scripts/suggest-compact.js +1 -1
  137. package/plugins/pbr/scripts/track-context-budget.js +53 -2
  138. package/plugins/pbr/scripts/validate-task.js +20 -28
  139. package/plugins/pbr/skills/audit/SKILL.md +19 -3
  140. package/plugins/pbr/skills/begin/SKILL.md +48 -2
  141. package/plugins/pbr/skills/build/SKILL.md +39 -2
  142. package/plugins/pbr/skills/config/SKILL.md +12 -2
  143. package/plugins/pbr/skills/debug/SKILL.md +12 -1
  144. package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +12 -1
  145. package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +12 -5
  146. package/plugins/pbr/skills/explore/SKILL.md +13 -2
  147. package/plugins/pbr/skills/health/SKILL.md +14 -3
  148. package/plugins/pbr/skills/help/SKILL.md +2 -0
  149. package/plugins/pbr/skills/import/SKILL.md +26 -1
  150. package/plugins/pbr/skills/milestone/SKILL.md +15 -3
  151. package/plugins/pbr/skills/plan/SKILL.md +52 -2
  152. package/plugins/pbr/skills/quick/SKILL.md +21 -0
  153. package/plugins/pbr/skills/review/SKILL.md +46 -0
  154. package/plugins/pbr/skills/scan/SKILL.md +20 -0
  155. package/plugins/pbr/skills/setup/SKILL.md +9 -1
  156. package/plugins/pbr/skills/shared/context-budget.md +10 -0
  157. package/plugins/pbr/skills/shared/universal-anti-patterns.md +6 -0
  158. package/plugins/pbr/skills/test/SKILL.md +212 -0
  159. package/plugins/pbr/templates/SUMMARY-complex.md.tmpl +95 -0
  160. package/plugins/pbr/templates/SUMMARY-minimal.md.tmpl +48 -0
@@ -0,0 +1,300 @@
1
+ /**
2
+ * lib/todo.js — File-based todo operations for Plan-Build-Run.
3
+ *
4
+ * Todos are individual .md files with YAML frontmatter stored in:
5
+ * .planning/todos/pending/{NNN}-{slug}.md — active todos
6
+ * .planning/todos/done/{NNN}-{slug}.md — completed todos
7
+ *
8
+ * Frontmatter fields: title, status, priority, source, created, completed, theme.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { parseYamlFrontmatter, atomicWrite } = require('./core');
14
+
15
+ // --- Helpers ---
16
+
17
+ /**
18
+ * Parse a todo filename into its components.
19
+ * @param {string} filename - e.g. "042-fix-login-bug.md"
20
+ * @returns {{ number: number, slug: string } | null}
21
+ */
22
+ function parseTodoFilename(filename) {
23
+ const match = filename.match(/^(\d+)-(.+)\.md$/);
24
+ if (!match) return null;
25
+ return { number: parseInt(match[1], 10), slug: match[2] };
26
+ }
27
+
28
+ /**
29
+ * Read and parse a single todo file.
30
+ * @param {string} filePath - Absolute path to the .md file
31
+ * @param {string} filename - Just the filename for parsing NNN-slug
32
+ * @returns {object} Todo object with number, slug, title, priority, theme, etc.
33
+ */
34
+ function parseTodoFile(filePath, filename) {
35
+ const parsed = parseTodoFilename(filename);
36
+ const content = fs.readFileSync(filePath, 'utf8');
37
+ const fm = parseYamlFrontmatter(content);
38
+
39
+ // Extract body (everything after frontmatter closing ---)
40
+ const bodyMatch = content.match(/^---[\s\S]*?\n---\s*\n?([\s\S]*)$/);
41
+ const body = bodyMatch ? bodyMatch[1].trim() : '';
42
+
43
+ return {
44
+ number: parsed ? parsed.number : null,
45
+ slug: parsed ? parsed.slug : filename.replace(/\.md$/, ''),
46
+ filename,
47
+ title: fm.title || '',
48
+ status: fm.status || 'pending',
49
+ priority: fm.priority || 'P2',
50
+ theme: fm.theme || 'general',
51
+ source: fm.source || 'conversation',
52
+ created: fm.created || null,
53
+ completed: fm.completed || null,
54
+ body
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Scan both pending/ and done/ to find the highest existing todo number.
60
+ * @param {string} planningDir - Path to .planning/
61
+ * @returns {number} Highest number found (0 if none)
62
+ */
63
+ function findHighestNumber(planningDir) {
64
+ let highest = 0;
65
+ const dirs = [
66
+ path.join(planningDir, 'todos', 'pending'),
67
+ path.join(planningDir, 'todos', 'done')
68
+ ];
69
+ for (const dir of dirs) {
70
+ if (!fs.existsSync(dir)) continue;
71
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.md'));
72
+ for (const file of files) {
73
+ const parsed = parseTodoFilename(file);
74
+ if (parsed && parsed.number > highest) {
75
+ highest = parsed.number;
76
+ }
77
+ }
78
+ }
79
+ return highest;
80
+ }
81
+
82
+ /**
83
+ * Generate a slug from a description.
84
+ * Takes first ~4 meaningful words, lowercase, hyphen-separated.
85
+ * @param {string} description
86
+ * @returns {string}
87
+ */
88
+ function generateSlug(description) {
89
+ const stopWords = new Set(['a', 'an', 'the', 'to', 'in', 'on', 'for', 'of', 'and', 'or', 'is', 'it', 'with']);
90
+ const words = description
91
+ .toLowerCase()
92
+ .replace(/[^a-z0-9\s-]/g, '')
93
+ .split(/\s+/)
94
+ .filter(w => w.length > 0 && !stopWords.has(w))
95
+ .slice(0, 4);
96
+ return words.join('-') || 'untitled';
97
+ }
98
+
99
+ // --- Core operations ---
100
+
101
+ /**
102
+ * List todos from a directory (pending or done).
103
+ * @param {string} planningDir - Path to .planning/
104
+ * @param {object} opts - { status: 'pending'|'done'|'all', theme: string|null }
105
+ * @returns {{ todos: object[], count: number }}
106
+ */
107
+ function todoList(planningDir, opts = {}) {
108
+ const status = opts.status || 'pending';
109
+ const theme = opts.theme || null;
110
+ const todos = [];
111
+
112
+ const dirsToScan = status === 'all'
113
+ ? ['pending', 'done']
114
+ : [status];
115
+
116
+ for (const subdir of dirsToScan) {
117
+ const dir = path.join(planningDir, 'todos', subdir);
118
+ if (!fs.existsSync(dir)) continue;
119
+
120
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.md')).sort();
121
+ for (const file of files) {
122
+ const todo = parseTodoFile(path.join(dir, file), file);
123
+ if (theme && todo.theme !== theme) continue;
124
+ todos.push(todo);
125
+ }
126
+ }
127
+
128
+ // Sort by number
129
+ todos.sort((a, b) => (a.number || 0) - (b.number || 0));
130
+
131
+ return { todos, count: todos.length };
132
+ }
133
+
134
+ /**
135
+ * Get a specific todo by its NNN number.
136
+ * Searches pending first, then done.
137
+ * @param {string} planningDir - Path to .planning/
138
+ * @param {number|string} num - The todo number (e.g. 42 or "042")
139
+ * @returns {object} Todo object with full body, or { error: "..." }
140
+ */
141
+ function todoGet(planningDir, num) {
142
+ const numStr = String(num).padStart(3, '0');
143
+ const prefix = numStr + '-';
144
+
145
+ for (const subdir of ['pending', 'done']) {
146
+ const dir = path.join(planningDir, 'todos', subdir);
147
+ if (!fs.existsSync(dir)) continue;
148
+
149
+ const files = fs.readdirSync(dir).filter(f => f.startsWith(prefix) && f.endsWith('.md'));
150
+ if (files.length > 0) {
151
+ const todo = parseTodoFile(path.join(dir, files[0]), files[0]);
152
+ todo.location = subdir;
153
+ return todo;
154
+ }
155
+ }
156
+
157
+ return { error: `Todo ${numStr} not found in pending or done` };
158
+ }
159
+
160
+ /**
161
+ * Add a new todo.
162
+ * @param {string} planningDir - Path to .planning/
163
+ * @param {string} title - The todo description
164
+ * @param {object} opts - { priority: 'P1'|'P2'|'P3', theme: string, source: string }
165
+ * @returns {{ success: boolean, number: number, filename: string, path: string } | { error: string }}
166
+ */
167
+ function todoAdd(planningDir, title, opts = {}) {
168
+ if (!title || !title.trim()) {
169
+ return { error: 'Title is required' };
170
+ }
171
+
172
+ const pendingDir = path.join(planningDir, 'todos', 'pending');
173
+
174
+ // Ensure directory exists
175
+ fs.mkdirSync(pendingDir, { recursive: true });
176
+
177
+ const nextNum = findHighestNumber(planningDir) + 1;
178
+ const numStr = String(nextNum).padStart(3, '0');
179
+ const slug = generateSlug(title);
180
+ const filename = `${numStr}-${slug}.md`;
181
+ const filePath = path.join(pendingDir, filename);
182
+
183
+ const priority = opts.priority || 'P2';
184
+ const theme = opts.theme || 'general';
185
+ const source = opts.source || 'cli';
186
+ const today = new Date().toISOString().slice(0, 10);
187
+
188
+ const content = [
189
+ '---',
190
+ `title: "${title.replace(/"/g, '\\"')}"`,
191
+ 'status: pending',
192
+ `priority: ${priority}`,
193
+ `source: ${source}`,
194
+ `created: ${today}`,
195
+ `theme: ${theme}`,
196
+ '---',
197
+ '',
198
+ '## Goal',
199
+ '',
200
+ title,
201
+ ''
202
+ ].join('\n');
203
+
204
+ const result = atomicWrite(filePath, content);
205
+ if (!result.success) {
206
+ return { error: `Failed to write todo file: ${result.error}` };
207
+ }
208
+
209
+ return {
210
+ success: true,
211
+ number: nextNum,
212
+ number_padded: numStr,
213
+ filename,
214
+ path: filePath
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Mark a todo as done. Moves from pending/ to done/.
220
+ * Write to done/ first, verify, then delete from pending/.
221
+ * @param {string} planningDir - Path to .planning/
222
+ * @param {number|string} num - The todo number
223
+ * @returns {{ success: boolean, title: string, filename: string } | { error: string }}
224
+ */
225
+ function todoDone(planningDir, num) {
226
+ const numStr = String(num).padStart(3, '0');
227
+ const prefix = numStr + '-';
228
+
229
+ const pendingDir = path.join(planningDir, 'todos', 'pending');
230
+ const doneDir = path.join(planningDir, 'todos', 'done');
231
+
232
+ if (!fs.existsSync(pendingDir)) {
233
+ return { error: `Todo ${numStr} not found — no pending directory` };
234
+ }
235
+
236
+ const files = fs.readdirSync(pendingDir).filter(f => f.startsWith(prefix) && f.endsWith('.md'));
237
+ if (files.length === 0) {
238
+ return { error: `Todo ${numStr} not found in pending todos` };
239
+ }
240
+
241
+ const filename = files[0];
242
+ const pendingPath = path.join(pendingDir, filename);
243
+ const content = fs.readFileSync(pendingPath, 'utf8');
244
+ const fm = parseYamlFrontmatter(content);
245
+ const title = fm.title || '';
246
+ const today = new Date().toISOString().slice(0, 10);
247
+
248
+ // Update frontmatter: status → done, add completed date
249
+ let updatedContent = content
250
+ .replace(/^status:\s*.*/m, 'status: done')
251
+ .replace(/^(---[\s\S]*?)(---)/, (match, front, close) => {
252
+ // Add completed field if not present
253
+ if (!/^completed:/m.test(front)) {
254
+ return front + `completed: ${today}\n${close}`;
255
+ }
256
+ return match.replace(/^completed:\s*.*/m, `completed: ${today}`);
257
+ });
258
+
259
+ // Ensure done/ directory exists
260
+ fs.mkdirSync(doneDir, { recursive: true });
261
+
262
+ // Write to done/ FIRST
263
+ const donePath = path.join(doneDir, filename);
264
+ const writeResult = atomicWrite(donePath, updatedContent);
265
+ if (!writeResult.success) {
266
+ return { error: `Failed to write to done/: ${writeResult.error}. Pending file preserved.` };
267
+ }
268
+
269
+ // Verify done/ write succeeded
270
+ if (!fs.existsSync(donePath)) {
271
+ return { error: 'Done file not found after write. Pending file preserved.' };
272
+ }
273
+
274
+ // Only now delete from pending/
275
+ try {
276
+ fs.unlinkSync(pendingPath);
277
+ } catch (e) {
278
+ // Non-fatal — todo is in done/ now, pending copy is just stale
279
+ return {
280
+ success: true,
281
+ title,
282
+ filename,
283
+ warning: `Completed but could not remove from pending/: ${e.message}`
284
+ };
285
+ }
286
+
287
+ return { success: true, title, filename };
288
+ }
289
+
290
+ module.exports = {
291
+ todoList,
292
+ todoGet,
293
+ todoAdd,
294
+ todoDone,
295
+ // Exported for testing
296
+ parseTodoFilename,
297
+ parseTodoFile,
298
+ findHighestNumber,
299
+ generateSlug
300
+ };