@sienklogic/plan-build-run 2.0.0 → 2.0.1

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 (225) hide show
  1. package/CHANGELOG.md +56 -56
  2. package/CLAUDE.md +149 -149
  3. package/LICENSE +21 -21
  4. package/README.md +247 -247
  5. package/dashboard/bin/cli.js +25 -25
  6. package/dashboard/package.json +34 -34
  7. package/dashboard/public/css/layout.css +406 -406
  8. package/dashboard/public/css/status-colors.css +98 -98
  9. package/dashboard/public/js/htmx-title.js +5 -5
  10. package/dashboard/public/js/sidebar-toggle.js +20 -20
  11. package/dashboard/src/app.js +78 -78
  12. package/dashboard/src/middleware/errorHandler.js +52 -52
  13. package/dashboard/src/middleware/notFoundHandler.js +9 -9
  14. package/dashboard/src/repositories/planning.repository.js +128 -128
  15. package/dashboard/src/routes/events.routes.js +40 -40
  16. package/dashboard/src/routes/index.routes.js +31 -31
  17. package/dashboard/src/routes/pages.routes.js +245 -195
  18. package/dashboard/src/server.js +42 -42
  19. package/dashboard/src/services/dashboard.service.js +222 -222
  20. package/dashboard/src/services/phase.service.js +220 -167
  21. package/dashboard/src/services/project.service.js +57 -57
  22. package/dashboard/src/services/roadmap.service.js +171 -171
  23. package/dashboard/src/services/sse.service.js +58 -58
  24. package/dashboard/src/services/todo.service.js +254 -254
  25. package/dashboard/src/services/watcher.service.js +48 -48
  26. package/dashboard/src/views/coming-soon.ejs +11 -11
  27. package/dashboard/src/views/error.ejs +13 -13
  28. package/dashboard/src/views/index.ejs +5 -5
  29. package/dashboard/src/views/layout.ejs +1 -1
  30. package/dashboard/src/views/partials/dashboard-content.ejs +77 -77
  31. package/dashboard/src/views/partials/footer.ejs +3 -3
  32. package/dashboard/src/views/partials/head.ejs +21 -21
  33. package/dashboard/src/views/partials/header.ejs +12 -12
  34. package/dashboard/src/views/partials/layout-bottom.ejs +15 -15
  35. package/dashboard/src/views/partials/layout-top.ejs +8 -8
  36. package/dashboard/src/views/partials/phase-content.ejs +188 -181
  37. package/dashboard/src/views/partials/phase-doc-content.ejs +38 -0
  38. package/dashboard/src/views/partials/phases-content.ejs +117 -117
  39. package/dashboard/src/views/partials/roadmap-content.ejs +142 -142
  40. package/dashboard/src/views/partials/sidebar.ejs +38 -38
  41. package/dashboard/src/views/partials/todo-create-content.ejs +53 -53
  42. package/dashboard/src/views/partials/todo-detail-content.ejs +38 -38
  43. package/dashboard/src/views/partials/todos-content.ejs +53 -53
  44. package/dashboard/src/views/phase-detail.ejs +5 -5
  45. package/dashboard/src/views/phase-doc.ejs +5 -0
  46. package/dashboard/src/views/phases.ejs +5 -5
  47. package/dashboard/src/views/roadmap.ejs +5 -5
  48. package/dashboard/src/views/todo-create.ejs +5 -5
  49. package/dashboard/src/views/todo-detail.ejs +5 -5
  50. package/dashboard/src/views/todos.ejs +5 -5
  51. package/package.json +57 -57
  52. package/plugins/pbr/.claude-plugin/plugin.json +13 -13
  53. package/plugins/pbr/UI-CONSISTENCY-GAPS.md +61 -61
  54. package/plugins/pbr/agents/codebase-mapper.md +279 -271
  55. package/plugins/pbr/agents/debugger.md +281 -281
  56. package/plugins/pbr/agents/executor.md +428 -407
  57. package/plugins/pbr/agents/general.md +164 -164
  58. package/plugins/pbr/agents/integration-checker.md +169 -141
  59. package/plugins/pbr/agents/plan-checker.md +296 -280
  60. package/plugins/pbr/agents/planner.md +358 -358
  61. package/plugins/pbr/agents/researcher.md +363 -363
  62. package/plugins/pbr/agents/synthesizer.md +230 -230
  63. package/plugins/pbr/agents/verifier.md +489 -454
  64. package/plugins/pbr/commands/begin.md +5 -5
  65. package/plugins/pbr/commands/build.md +5 -5
  66. package/plugins/pbr/commands/config.md +5 -5
  67. package/plugins/pbr/commands/continue.md +5 -5
  68. package/plugins/pbr/commands/debug.md +5 -5
  69. package/plugins/pbr/commands/discuss.md +5 -5
  70. package/plugins/pbr/commands/explore.md +5 -5
  71. package/plugins/pbr/commands/health.md +5 -5
  72. package/plugins/pbr/commands/help.md +5 -5
  73. package/plugins/pbr/commands/import.md +5 -5
  74. package/plugins/pbr/commands/milestone.md +5 -5
  75. package/plugins/pbr/commands/note.md +5 -5
  76. package/plugins/pbr/commands/pause.md +5 -5
  77. package/plugins/pbr/commands/plan.md +5 -5
  78. package/plugins/pbr/commands/quick.md +5 -5
  79. package/plugins/pbr/commands/resume.md +5 -5
  80. package/plugins/pbr/commands/review.md +5 -5
  81. package/plugins/pbr/commands/scan.md +5 -5
  82. package/plugins/pbr/commands/setup.md +5 -5
  83. package/plugins/pbr/commands/status.md +5 -5
  84. package/plugins/pbr/commands/todo.md +5 -5
  85. package/plugins/pbr/contexts/dev.md +27 -27
  86. package/plugins/pbr/contexts/research.md +28 -28
  87. package/plugins/pbr/contexts/review.md +36 -36
  88. package/plugins/pbr/hooks/hooks.json +183 -183
  89. package/plugins/pbr/references/agent-anti-patterns.md +24 -24
  90. package/plugins/pbr/references/agent-interactions.md +134 -134
  91. package/plugins/pbr/references/agent-teams.md +54 -54
  92. package/plugins/pbr/references/checkpoints.md +157 -157
  93. package/plugins/pbr/references/common-bug-patterns.md +13 -13
  94. package/plugins/pbr/references/config-reference.md +441 -0
  95. package/plugins/pbr/references/continuation-format.md +212 -212
  96. package/plugins/pbr/references/deviation-rules.md +112 -112
  97. package/plugins/pbr/references/git-integration.md +226 -226
  98. package/plugins/pbr/references/integration-patterns.md +117 -117
  99. package/plugins/pbr/references/model-profiles.md +99 -99
  100. package/plugins/pbr/references/model-selection.md +31 -31
  101. package/plugins/pbr/references/pbr-rules.md +193 -193
  102. package/plugins/pbr/references/plan-authoring.md +181 -181
  103. package/plugins/pbr/references/plan-format.md +287 -283
  104. package/plugins/pbr/references/planning-config.md +213 -213
  105. package/plugins/pbr/references/questioning.md +214 -214
  106. package/plugins/pbr/references/reading-verification.md +127 -127
  107. package/plugins/pbr/references/stub-patterns.md +160 -160
  108. package/plugins/pbr/references/subagent-coordination.md +119 -119
  109. package/plugins/pbr/references/ui-formatting.md +461 -399
  110. package/plugins/pbr/references/verification-patterns.md +198 -198
  111. package/plugins/pbr/references/wave-execution.md +95 -95
  112. package/plugins/pbr/scripts/auto-continue.js +80 -80
  113. package/plugins/pbr/scripts/check-dangerous-commands.js +136 -136
  114. package/plugins/pbr/scripts/check-doc-sprawl.js +102 -102
  115. package/plugins/pbr/scripts/check-phase-boundary.js +196 -196
  116. package/plugins/pbr/scripts/check-plan-format.js +270 -270
  117. package/plugins/pbr/scripts/check-roadmap-sync.js +322 -252
  118. package/plugins/pbr/scripts/check-skill-workflow.js +262 -262
  119. package/plugins/pbr/scripts/check-state-sync.js +476 -476
  120. package/plugins/pbr/scripts/check-subagent-output.js +144 -144
  121. package/plugins/pbr/scripts/config-schema.json +251 -251
  122. package/plugins/pbr/scripts/context-budget-check.js +287 -287
  123. package/plugins/pbr/scripts/event-handler.js +151 -151
  124. package/plugins/pbr/scripts/event-logger.js +92 -92
  125. package/plugins/pbr/scripts/hook-logger.js +80 -76
  126. package/plugins/pbr/scripts/hooks-schema.json +79 -79
  127. package/plugins/pbr/scripts/log-subagent.js +164 -152
  128. package/plugins/pbr/scripts/log-tool-failure.js +88 -88
  129. package/plugins/pbr/scripts/pbr-tools.js +1378 -1301
  130. package/plugins/pbr/scripts/post-write-dispatch.js +66 -66
  131. package/plugins/pbr/scripts/post-write-quality.js +207 -207
  132. package/plugins/pbr/scripts/pre-bash-dispatch.js +86 -56
  133. package/plugins/pbr/scripts/pre-write-dispatch.js +97 -62
  134. package/plugins/pbr/scripts/progress-tracker.js +281 -228
  135. package/plugins/pbr/scripts/run-hook.js +92 -0
  136. package/plugins/pbr/scripts/session-cleanup.js +254 -254
  137. package/plugins/pbr/scripts/status-line.js +288 -285
  138. package/plugins/pbr/scripts/suggest-compact.js +119 -119
  139. package/plugins/pbr/scripts/task-completed.js +45 -45
  140. package/plugins/pbr/scripts/track-context-budget.js +149 -119
  141. package/plugins/pbr/scripts/validate-commit.js +200 -200
  142. package/plugins/pbr/scripts/validate-plugin-structure.js +183 -172
  143. package/plugins/pbr/scripts/validate-task.js +106 -0
  144. package/plugins/pbr/skills/begin/SKILL.md +594 -545
  145. package/plugins/pbr/skills/begin/templates/PROJECT.md.tmpl +33 -33
  146. package/plugins/pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -18
  147. package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +49 -49
  148. package/plugins/pbr/skills/begin/templates/config.json.tmpl +64 -63
  149. package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +19 -19
  150. package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +30 -30
  151. package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +16 -16
  152. package/plugins/pbr/skills/build/SKILL.md +943 -962
  153. package/plugins/pbr/skills/config/SKILL.md +256 -241
  154. package/plugins/pbr/skills/continue/SKILL.md +164 -127
  155. package/plugins/pbr/skills/debug/SKILL.md +515 -489
  156. package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +16 -16
  157. package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +27 -27
  158. package/plugins/pbr/skills/discuss/SKILL.md +347 -338
  159. package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +61 -61
  160. package/plugins/pbr/skills/discuss/templates/decision-categories.md +9 -9
  161. package/plugins/pbr/skills/explore/SKILL.md +378 -362
  162. package/plugins/pbr/skills/health/SKILL.md +221 -186
  163. package/plugins/pbr/skills/health/templates/check-pattern.md.tmpl +30 -30
  164. package/plugins/pbr/skills/health/templates/output-format.md.tmpl +63 -63
  165. package/plugins/pbr/skills/help/SKILL.md +155 -140
  166. package/plugins/pbr/skills/import/SKILL.md +504 -490
  167. package/plugins/pbr/skills/milestone/SKILL.md +704 -673
  168. package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +48 -48
  169. package/plugins/pbr/skills/milestone/templates/stats-file.md.tmpl +30 -30
  170. package/plugins/pbr/skills/note/SKILL.md +231 -212
  171. package/plugins/pbr/skills/pause/SKILL.md +249 -235
  172. package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +71 -71
  173. package/plugins/pbr/skills/plan/SKILL.md +685 -628
  174. package/plugins/pbr/skills/plan/decimal-phase-calc.md +98 -98
  175. package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +21 -21
  176. package/plugins/pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -32
  177. package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +38 -38
  178. package/plugins/pbr/skills/plan/templates/researcher-prompt.md.tmpl +19 -19
  179. package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +23 -23
  180. package/plugins/pbr/skills/quick/SKILL.md +354 -335
  181. package/plugins/pbr/skills/resume/SKILL.md +402 -388
  182. package/plugins/pbr/skills/review/SKILL.md +686 -652
  183. package/plugins/pbr/skills/review/templates/debugger-prompt.md.tmpl +60 -60
  184. package/plugins/pbr/skills/review/templates/gap-planner-prompt.md.tmpl +40 -40
  185. package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +115 -115
  186. package/plugins/pbr/skills/scan/SKILL.md +304 -269
  187. package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +201 -201
  188. package/plugins/pbr/skills/setup/SKILL.md +253 -227
  189. package/plugins/pbr/skills/shared/commit-planning-docs.md +35 -35
  190. package/plugins/pbr/skills/shared/config-loading.md +102 -102
  191. package/plugins/pbr/skills/shared/context-budget.md +40 -40
  192. package/plugins/pbr/skills/shared/context-loader-task.md +86 -86
  193. package/plugins/pbr/skills/shared/digest-select.md +79 -79
  194. package/plugins/pbr/skills/shared/domain-probes.md +125 -125
  195. package/plugins/pbr/skills/shared/error-reporting.md +79 -79
  196. package/plugins/pbr/skills/shared/gate-prompts.md +388 -388
  197. package/plugins/pbr/skills/shared/phase-argument-parsing.md +45 -45
  198. package/plugins/pbr/skills/shared/progress-display.md +53 -53
  199. package/plugins/pbr/skills/shared/revision-loop.md +81 -81
  200. package/plugins/pbr/skills/shared/state-loading.md +62 -62
  201. package/plugins/pbr/skills/shared/state-update.md +161 -161
  202. package/plugins/pbr/skills/shared/universal-anti-patterns.md +33 -33
  203. package/plugins/pbr/skills/status/SKILL.md +367 -353
  204. package/plugins/pbr/skills/todo/SKILL.md +198 -181
  205. package/plugins/pbr/templates/CONTEXT.md.tmpl +52 -52
  206. package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +151 -151
  207. package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +97 -97
  208. package/plugins/pbr/templates/ROADMAP.md.tmpl +40 -40
  209. package/plugins/pbr/templates/SUMMARY.md.tmpl +81 -81
  210. package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +116 -116
  211. package/plugins/pbr/templates/codebase/ARCHITECTURE.md.tmpl +98 -98
  212. package/plugins/pbr/templates/codebase/CONCERNS.md.tmpl +93 -93
  213. package/plugins/pbr/templates/codebase/CONVENTIONS.md.tmpl +104 -104
  214. package/plugins/pbr/templates/codebase/INTEGRATIONS.md.tmpl +78 -78
  215. package/plugins/pbr/templates/codebase/STACK.md.tmpl +78 -78
  216. package/plugins/pbr/templates/codebase/STRUCTURE.md.tmpl +80 -80
  217. package/plugins/pbr/templates/codebase/TESTING.md.tmpl +107 -107
  218. package/plugins/pbr/templates/continue-here.md.tmpl +73 -73
  219. package/plugins/pbr/templates/prompt-partials/phase-project-context.md.tmpl +37 -37
  220. package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +124 -124
  221. package/plugins/pbr/templates/research/STACK.md.tmpl +71 -71
  222. package/plugins/pbr/templates/research/SUMMARY.md.tmpl +112 -112
  223. package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +81 -81
  224. package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +99 -99
  225. package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +36 -36
@@ -1,172 +1,183 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Validates the Plan-Build-Run plugin structure:
5
- * - Every skill directory has SKILL.md
6
- * - Every agent file has valid YAML frontmatter (name, description)
7
- * - hooks.json references existing scripts
8
- * - No broken relative links in markdown files
9
- */
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
-
14
- const ROOT = path.resolve(__dirname, '..');
15
- let errors = 0;
16
- let warnings = 0;
17
-
18
- function error(msg) {
19
- console.error(`ERROR: ${msg}`);
20
- errors++;
21
- }
22
-
23
- function warn(msg) {
24
- console.warn(`WARN: ${msg}`);
25
- warnings++;
26
- }
27
-
28
- function info(msg) {
29
- console.log(`OK: ${msg}`);
30
- }
31
-
32
- // 1. Check plugin.json exists
33
- const pluginJsonPath = path.join(ROOT, '.claude-plugin', 'plugin.json');
34
- if (!fs.existsSync(pluginJsonPath)) {
35
- error('.claude-plugin/plugin.json missing');
36
- } else {
37
- try {
38
- const plugin = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
39
- if (!plugin.name) error('plugin.json missing "name" field');
40
- if (!plugin.version) error('plugin.json missing "version" field');
41
- if (!plugin.description) error('plugin.json missing "description" field');
42
- info(`Plugin: ${plugin.name} v${plugin.version}`);
43
- } catch (e) {
44
- error(`plugin.json is not valid JSON: ${e.message}`);
45
- }
46
- }
47
-
48
- // 2. Check every skill directory has SKILL.md
49
- const skillsDir = path.join(ROOT, 'skills');
50
- if (fs.existsSync(skillsDir)) {
51
- const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
52
- .filter(d => d.isDirectory() && d.name !== 'shared');
53
-
54
- for (const dir of skillDirs) {
55
- const skillMd = path.join(skillsDir, dir.name, 'SKILL.md');
56
- if (!fs.existsSync(skillMd)) {
57
- error(`skills/${dir.name}/ missing SKILL.md`);
58
- } else {
59
- const content = fs.readFileSync(skillMd, 'utf8');
60
- if (!content.startsWith('---')) {
61
- error(`skills/${dir.name}/SKILL.md missing YAML frontmatter`);
62
- } else {
63
- const frontmatter = content.split('---')[1];
64
- if (!frontmatter.includes('name:')) {
65
- error(`skills/${dir.name}/SKILL.md frontmatter missing "name" field`);
66
- }
67
- if (!frontmatter.includes('description:')) {
68
- error(`skills/${dir.name}/SKILL.md frontmatter missing "description" field`);
69
- }
70
- }
71
- // Check: skills with Task in allowed-tools must have Context Budget section
72
- const frontmatterBlock = content.split('---')[1] || '';
73
- const hasTaskTool = /allowed-tools:.*Task/.test(frontmatterBlock);
74
- if (hasTaskTool && !content.includes('## Context Budget')) {
75
- warn(`skills/${dir.name}/SKILL.md has Task in allowed-tools but no "## Context Budget" section`);
76
- }
77
-
78
- info(`Skill: /pbr:${dir.name}`);
79
- }
80
- }
81
- } else {
82
- error('skills/ directory missing');
83
- }
84
-
85
- // 3. Check every agent file has valid frontmatter
86
- const agentsDir = path.join(ROOT, 'agents');
87
- if (fs.existsSync(agentsDir)) {
88
- const agentFiles = fs.readdirSync(agentsDir)
89
- .filter(f => f.endsWith('.md'));
90
-
91
- for (const file of agentFiles) {
92
- const content = fs.readFileSync(path.join(agentsDir, file), 'utf8');
93
- if (!content.startsWith('---')) {
94
- error(`agents/${file} missing YAML frontmatter`);
95
- } else {
96
- const frontmatter = content.split('---')[1];
97
- if (!frontmatter.includes('name:')) {
98
- error(`agents/${file} frontmatter missing "name" field`);
99
- }
100
- if (!frontmatter.includes('description:')) {
101
- error(`agents/${file} frontmatter missing "description" field`);
102
- }
103
- const nameMatch = frontmatter.match(/name:\s*(.+)/);
104
- info(`Agent: ${nameMatch ? nameMatch[1].trim() : file}`);
105
- }
106
- }
107
- } else {
108
- error('agents/ directory missing');
109
- }
110
-
111
- // 4. Check context files have valid structure
112
- const contextsDir = path.join(ROOT, 'contexts');
113
- if (fs.existsSync(contextsDir)) {
114
- const contextFiles = fs.readdirSync(contextsDir)
115
- .filter(f => f.endsWith('.md'));
116
-
117
- for (const file of contextFiles) {
118
- const content = fs.readFileSync(path.join(contextsDir, file), 'utf8');
119
- if (!content.startsWith('#')) {
120
- warn(`contexts/${file} should start with a heading`);
121
- }
122
- const name = file.replace('.md', '');
123
- info(`Context: ${name}`);
124
- }
125
- } else {
126
- warn('contexts/ directory not found (contexts are optional)');
127
- }
128
-
129
- // 5. Check hooks.json references existing scripts
130
- const hooksJsonPath = path.join(ROOT, 'hooks', 'hooks.json');
131
- if (fs.existsSync(hooksJsonPath)) {
132
- try {
133
- const hooksFile = JSON.parse(fs.readFileSync(hooksJsonPath, 'utf8'));
134
-
135
- // Plugin hooks format: { hooks: { EventName: [ { matcher?, hooks: [ { type, command } ] } ] } }
136
- const hooksObj = hooksFile.hooks || {};
137
- for (const eventName of Object.keys(hooksObj)) {
138
- const matcherGroups = hooksObj[eventName];
139
- if (!Array.isArray(matcherGroups)) continue;
140
- for (const group of matcherGroups) {
141
- const handlers = group.hooks || [];
142
- for (const handler of handlers) {
143
- if (handler.command) {
144
- const cmd = handler.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, ROOT);
145
- const parts = cmd.split(' ');
146
- const scriptPart = parts.find(p => p.endsWith('.js'));
147
- if (scriptPart) {
148
- const scriptPath = scriptPart.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, ROOT);
149
- const resolvedPath = path.isAbsolute(scriptPath) ? scriptPath : path.join(ROOT, scriptPath);
150
- if (!fs.existsSync(resolvedPath)) {
151
- error(`hooks.json references missing script: ${scriptPart}`);
152
- }
153
- }
154
- }
155
- }
156
- }
157
- }
158
- info('hooks.json validated');
159
- } catch (e) {
160
- error(`hooks.json is not valid JSON: ${e.message}`);
161
- }
162
- } else {
163
- warn('hooks/hooks.json not found (hooks are optional)');
164
- }
165
-
166
- // 6. Summary
167
- console.log('\n---');
168
- console.log(`Validation complete: ${errors} errors, ${warnings} warnings`);
169
-
170
- if (errors > 0) {
171
- process.exit(1);
172
- }
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Validates the Plan-Build-Run plugin structure:
5
+ * - Every skill directory has SKILL.md
6
+ * - Every agent file has valid YAML frontmatter (name, description)
7
+ * - hooks.json references existing scripts
8
+ * - No broken relative links in markdown files
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const ROOT = path.resolve(__dirname, '..');
15
+ let errors = 0;
16
+ let warnings = 0;
17
+
18
+ function error(msg) {
19
+ console.error(`ERROR: ${msg}`);
20
+ errors++;
21
+ }
22
+
23
+ function warn(msg) {
24
+ console.warn(`WARN: ${msg}`);
25
+ warnings++;
26
+ }
27
+
28
+ function info(msg) {
29
+ console.log(`OK: ${msg}`);
30
+ }
31
+
32
+ // 1. Check plugin.json exists
33
+ const pluginJsonPath = path.join(ROOT, '.claude-plugin', 'plugin.json');
34
+ if (!fs.existsSync(pluginJsonPath)) {
35
+ error('.claude-plugin/plugin.json missing');
36
+ } else {
37
+ try {
38
+ const plugin = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
39
+ if (!plugin.name) error('plugin.json missing "name" field');
40
+ if (!plugin.version) error('plugin.json missing "version" field');
41
+ if (!plugin.description) error('plugin.json missing "description" field');
42
+ info(`Plugin: ${plugin.name} v${plugin.version}`);
43
+ } catch (e) {
44
+ error(`plugin.json is not valid JSON: ${e.message}`);
45
+ }
46
+ }
47
+
48
+ // 2. Check every skill directory has SKILL.md
49
+ const skillsDir = path.join(ROOT, 'skills');
50
+ if (fs.existsSync(skillsDir)) {
51
+ const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
52
+ .filter(d => d.isDirectory() && d.name !== 'shared');
53
+
54
+ for (const dir of skillDirs) {
55
+ const skillMd = path.join(skillsDir, dir.name, 'SKILL.md');
56
+ if (!fs.existsSync(skillMd)) {
57
+ error(`skills/${dir.name}/ missing SKILL.md`);
58
+ } else {
59
+ const content = fs.readFileSync(skillMd, 'utf8');
60
+ if (!content.startsWith('---')) {
61
+ error(`skills/${dir.name}/SKILL.md missing YAML frontmatter`);
62
+ } else {
63
+ const frontmatter = content.split('---')[1];
64
+ if (!frontmatter.includes('name:')) {
65
+ error(`skills/${dir.name}/SKILL.md frontmatter missing "name" field`);
66
+ }
67
+ if (!frontmatter.includes('description:')) {
68
+ error(`skills/${dir.name}/SKILL.md frontmatter missing "description" field`);
69
+ }
70
+ }
71
+ // Check: skills with Task in allowed-tools must have Context Budget section
72
+ const frontmatterBlock = content.split('---')[1] || '';
73
+ const hasTaskTool = /allowed-tools:.*Task/.test(frontmatterBlock);
74
+ if (hasTaskTool && !content.includes('## Context Budget')) {
75
+ warn(`skills/${dir.name}/SKILL.md has Task in allowed-tools but no "## Context Budget" section`);
76
+ }
77
+
78
+ info(`Skill: /pbr:${dir.name}`);
79
+ }
80
+ }
81
+ } else {
82
+ error('skills/ directory missing');
83
+ }
84
+
85
+ // 3. Check every agent file has valid frontmatter
86
+ const agentsDir = path.join(ROOT, 'agents');
87
+ if (fs.existsSync(agentsDir)) {
88
+ const agentFiles = fs.readdirSync(agentsDir)
89
+ .filter(f => f.endsWith('.md'));
90
+
91
+ for (const file of agentFiles) {
92
+ const content = fs.readFileSync(path.join(agentsDir, file), 'utf8');
93
+ if (!content.startsWith('---')) {
94
+ error(`agents/${file} missing YAML frontmatter`);
95
+ } else {
96
+ const frontmatter = content.split('---')[1];
97
+ if (!frontmatter.includes('name:')) {
98
+ error(`agents/${file} frontmatter missing "name" field`);
99
+ }
100
+ if (!frontmatter.includes('description:')) {
101
+ error(`agents/${file} frontmatter missing "description" field`);
102
+ }
103
+ const nameMatch = frontmatter.match(/name:\s*(.+)/);
104
+ info(`Agent: ${nameMatch ? nameMatch[1].trim() : file}`);
105
+ }
106
+ }
107
+ } else {
108
+ error('agents/ directory missing');
109
+ }
110
+
111
+ // 4. Check context files have valid structure
112
+ const contextsDir = path.join(ROOT, 'contexts');
113
+ if (fs.existsSync(contextsDir)) {
114
+ const contextFiles = fs.readdirSync(contextsDir)
115
+ .filter(f => f.endsWith('.md'));
116
+
117
+ for (const file of contextFiles) {
118
+ const content = fs.readFileSync(path.join(contextsDir, file), 'utf8');
119
+ if (!content.startsWith('#')) {
120
+ warn(`contexts/${file} should start with a heading`);
121
+ }
122
+ const name = file.replace('.md', '');
123
+ info(`Context: ${name}`);
124
+ }
125
+ } else {
126
+ warn('contexts/ directory not found (contexts are optional)');
127
+ }
128
+
129
+ // 5. Check hooks.json references existing scripts
130
+ const hooksJsonPath = path.join(ROOT, 'hooks', 'hooks.json');
131
+ if (fs.existsSync(hooksJsonPath)) {
132
+ try {
133
+ const hooksFile = JSON.parse(fs.readFileSync(hooksJsonPath, 'utf8'));
134
+
135
+ // Plugin hooks format: { hooks: { EventName: [ { matcher?, hooks: [ { type, command } ] } ] } }
136
+ const hooksObj = hooksFile.hooks || {};
137
+ for (const eventName of Object.keys(hooksObj)) {
138
+ const matcherGroups = hooksObj[eventName];
139
+ if (!Array.isArray(matcherGroups)) continue;
140
+ for (const group of matcherGroups) {
141
+ const handlers = group.hooks || [];
142
+ for (const handler of handlers) {
143
+ if (handler.command) {
144
+ const cmd = handler.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, ROOT);
145
+ // Handle run-hook.js bootstrap pattern: command ends with "run-hook.js\" scriptName.js"
146
+ const runHookMatch = cmd.match(/run-hook\.js[^"]*\)?\)?"?\s+(\S+\.js)/);
147
+ if (runHookMatch) {
148
+ // Script is dispatched via run-hook.js — look in scripts/ dir
149
+ const scriptName = runHookMatch[1];
150
+ const resolvedPath = path.join(ROOT, 'scripts', scriptName);
151
+ if (!fs.existsSync(resolvedPath)) {
152
+ error(`hooks.json references missing script: ${scriptName}`);
153
+ }
154
+ } else {
155
+ const parts = cmd.split(' ');
156
+ const scriptPart = parts.find(p => p.endsWith('.js'));
157
+ if (scriptPart) {
158
+ const scriptPath = scriptPart.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, ROOT);
159
+ const resolvedPath = path.isAbsolute(scriptPath) ? scriptPath : path.join(ROOT, scriptPath);
160
+ if (!fs.existsSync(resolvedPath)) {
161
+ error(`hooks.json references missing script: ${scriptPart}`);
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+ }
169
+ info('hooks.json validated');
170
+ } catch (e) {
171
+ error(`hooks.json is not valid JSON: ${e.message}`);
172
+ }
173
+ } else {
174
+ warn('hooks/hooks.json not found (hooks are optional)');
175
+ }
176
+
177
+ // 6. Summary
178
+ console.log('\n---');
179
+ console.log(`Validation complete: ${errors} errors, ${warnings} warnings`);
180
+
181
+ if (errors > 0) {
182
+ process.exit(1);
183
+ }
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PreToolUse hook: Validates Task() calls before execution.
5
+ *
6
+ * Advisory checks (exit 0 always, logs warnings):
7
+ * - description exists and is non-empty
8
+ * - description is reasonably short (<=100 chars)
9
+ * - subagent_type is a known pbr: agent type when applicable
10
+ *
11
+ * Exit codes:
12
+ * 0 = always (advisory only, never blocks)
13
+ */
14
+
15
+ const { logHook } = require('./hook-logger');
16
+
17
+ const KNOWN_AGENTS = [
18
+ 'researcher',
19
+ 'planner',
20
+ 'plan-checker',
21
+ 'executor',
22
+ 'verifier',
23
+ 'integration-checker',
24
+ 'debugger',
25
+ 'codebase-mapper',
26
+ 'synthesizer',
27
+ 'general'
28
+ ];
29
+
30
+ const MAX_DESCRIPTION_LENGTH = 100;
31
+
32
+ /**
33
+ * Check a parsed hook data object for Task() validation issues.
34
+ * Returns an array of warning strings (empty if all good).
35
+ */
36
+ function checkTask(data) {
37
+ const warnings = [];
38
+ const toolInput = data.tool_input || {};
39
+
40
+ const description = toolInput.description;
41
+ const subagentType = toolInput.subagent_type;
42
+
43
+ // Check description exists and is non-empty
44
+ if (!description || (typeof description === 'string' && !description.trim())) {
45
+ warnings.push('Task() called without a description. Descriptions help track agent purpose.');
46
+ } else if (typeof description === 'string') {
47
+ // Check description length
48
+ if (description.length > MAX_DESCRIPTION_LENGTH) {
49
+ warnings.push(
50
+ `Task() description is ${description.length} chars (recommended <=100). ` +
51
+ 'Keep descriptions to 3-5 words.'
52
+ );
53
+ }
54
+
55
+ // If description mentions pbr: patterns but no subagent_type is set
56
+ if (/\bpbr:/.test(description) && !subagentType) {
57
+ warnings.push(
58
+ 'Task() description contains "pbr:" but no subagent_type is set. ' +
59
+ 'Use subagent_type: "pbr:{name}" for automatic agent loading.'
60
+ );
61
+ }
62
+ }
63
+
64
+ // Validate subagent_type if it starts with pbr:
65
+ if (typeof subagentType === 'string' && subagentType.startsWith('pbr:')) {
66
+ const agentName = subagentType.slice(4);
67
+ if (!KNOWN_AGENTS.includes(agentName)) {
68
+ warnings.push(
69
+ `Unknown pbr agent type: "${subagentType}". ` +
70
+ `Known types: ${KNOWN_AGENTS.map(a => 'pbr:' + a).join(', ')}`
71
+ );
72
+ }
73
+ }
74
+
75
+ return warnings;
76
+ }
77
+
78
+ function main() {
79
+ let input = '';
80
+
81
+ process.stdin.setEncoding('utf8');
82
+ process.stdin.on('data', (chunk) => { input += chunk; });
83
+ process.stdin.on('end', () => {
84
+ try {
85
+ const data = JSON.parse(input);
86
+ const warnings = checkTask(data);
87
+
88
+ if (warnings.length > 0) {
89
+ for (const warning of warnings) {
90
+ logHook('validate-task', 'PreToolUse', 'warn', { warning });
91
+ }
92
+ process.stdout.write(JSON.stringify({
93
+ additionalContext: 'Task() validation warnings:\n' + warnings.map(w => '- ' + w).join('\n')
94
+ }));
95
+ }
96
+
97
+ process.exit(0);
98
+ } catch (_e) {
99
+ // Parse error — don't block
100
+ process.exit(0);
101
+ }
102
+ });
103
+ }
104
+
105
+ module.exports = { checkTask, KNOWN_AGENTS, MAX_DESCRIPTION_LENGTH };
106
+ if (require.main === module) { main(); }