@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.
- package/CHANGELOG.md +56 -56
- package/CLAUDE.md +149 -149
- package/LICENSE +21 -21
- package/README.md +247 -247
- package/dashboard/bin/cli.js +25 -25
- package/dashboard/package.json +34 -34
- package/dashboard/public/css/layout.css +406 -406
- package/dashboard/public/css/status-colors.css +98 -98
- package/dashboard/public/js/htmx-title.js +5 -5
- package/dashboard/public/js/sidebar-toggle.js +20 -20
- package/dashboard/src/app.js +78 -78
- package/dashboard/src/middleware/errorHandler.js +52 -52
- package/dashboard/src/middleware/notFoundHandler.js +9 -9
- package/dashboard/src/repositories/planning.repository.js +128 -128
- package/dashboard/src/routes/events.routes.js +40 -40
- package/dashboard/src/routes/index.routes.js +31 -31
- package/dashboard/src/routes/pages.routes.js +245 -195
- package/dashboard/src/server.js +42 -42
- package/dashboard/src/services/dashboard.service.js +222 -222
- package/dashboard/src/services/phase.service.js +220 -167
- package/dashboard/src/services/project.service.js +57 -57
- package/dashboard/src/services/roadmap.service.js +171 -171
- package/dashboard/src/services/sse.service.js +58 -58
- package/dashboard/src/services/todo.service.js +254 -254
- package/dashboard/src/services/watcher.service.js +48 -48
- package/dashboard/src/views/coming-soon.ejs +11 -11
- package/dashboard/src/views/error.ejs +13 -13
- package/dashboard/src/views/index.ejs +5 -5
- package/dashboard/src/views/layout.ejs +1 -1
- package/dashboard/src/views/partials/dashboard-content.ejs +77 -77
- package/dashboard/src/views/partials/footer.ejs +3 -3
- package/dashboard/src/views/partials/head.ejs +21 -21
- package/dashboard/src/views/partials/header.ejs +12 -12
- package/dashboard/src/views/partials/layout-bottom.ejs +15 -15
- package/dashboard/src/views/partials/layout-top.ejs +8 -8
- package/dashboard/src/views/partials/phase-content.ejs +188 -181
- package/dashboard/src/views/partials/phase-doc-content.ejs +38 -0
- package/dashboard/src/views/partials/phases-content.ejs +117 -117
- package/dashboard/src/views/partials/roadmap-content.ejs +142 -142
- package/dashboard/src/views/partials/sidebar.ejs +38 -38
- package/dashboard/src/views/partials/todo-create-content.ejs +53 -53
- package/dashboard/src/views/partials/todo-detail-content.ejs +38 -38
- package/dashboard/src/views/partials/todos-content.ejs +53 -53
- package/dashboard/src/views/phase-detail.ejs +5 -5
- package/dashboard/src/views/phase-doc.ejs +5 -0
- package/dashboard/src/views/phases.ejs +5 -5
- package/dashboard/src/views/roadmap.ejs +5 -5
- package/dashboard/src/views/todo-create.ejs +5 -5
- package/dashboard/src/views/todo-detail.ejs +5 -5
- package/dashboard/src/views/todos.ejs +5 -5
- package/package.json +57 -57
- package/plugins/pbr/.claude-plugin/plugin.json +13 -13
- package/plugins/pbr/UI-CONSISTENCY-GAPS.md +61 -61
- package/plugins/pbr/agents/codebase-mapper.md +279 -271
- package/plugins/pbr/agents/debugger.md +281 -281
- package/plugins/pbr/agents/executor.md +428 -407
- package/plugins/pbr/agents/general.md +164 -164
- package/plugins/pbr/agents/integration-checker.md +169 -141
- package/plugins/pbr/agents/plan-checker.md +296 -280
- package/plugins/pbr/agents/planner.md +358 -358
- package/plugins/pbr/agents/researcher.md +363 -363
- package/plugins/pbr/agents/synthesizer.md +230 -230
- package/plugins/pbr/agents/verifier.md +489 -454
- package/plugins/pbr/commands/begin.md +5 -5
- package/plugins/pbr/commands/build.md +5 -5
- package/plugins/pbr/commands/config.md +5 -5
- package/plugins/pbr/commands/continue.md +5 -5
- package/plugins/pbr/commands/debug.md +5 -5
- package/plugins/pbr/commands/discuss.md +5 -5
- package/plugins/pbr/commands/explore.md +5 -5
- package/plugins/pbr/commands/health.md +5 -5
- package/plugins/pbr/commands/help.md +5 -5
- package/plugins/pbr/commands/import.md +5 -5
- package/plugins/pbr/commands/milestone.md +5 -5
- package/plugins/pbr/commands/note.md +5 -5
- package/plugins/pbr/commands/pause.md +5 -5
- package/plugins/pbr/commands/plan.md +5 -5
- package/plugins/pbr/commands/quick.md +5 -5
- package/plugins/pbr/commands/resume.md +5 -5
- package/plugins/pbr/commands/review.md +5 -5
- package/plugins/pbr/commands/scan.md +5 -5
- package/plugins/pbr/commands/setup.md +5 -5
- package/plugins/pbr/commands/status.md +5 -5
- package/plugins/pbr/commands/todo.md +5 -5
- package/plugins/pbr/contexts/dev.md +27 -27
- package/plugins/pbr/contexts/research.md +28 -28
- package/plugins/pbr/contexts/review.md +36 -36
- package/plugins/pbr/hooks/hooks.json +183 -183
- package/plugins/pbr/references/agent-anti-patterns.md +24 -24
- package/plugins/pbr/references/agent-interactions.md +134 -134
- package/plugins/pbr/references/agent-teams.md +54 -54
- package/plugins/pbr/references/checkpoints.md +157 -157
- package/plugins/pbr/references/common-bug-patterns.md +13 -13
- package/plugins/pbr/references/config-reference.md +441 -0
- package/plugins/pbr/references/continuation-format.md +212 -212
- package/plugins/pbr/references/deviation-rules.md +112 -112
- package/plugins/pbr/references/git-integration.md +226 -226
- package/plugins/pbr/references/integration-patterns.md +117 -117
- package/plugins/pbr/references/model-profiles.md +99 -99
- package/plugins/pbr/references/model-selection.md +31 -31
- package/plugins/pbr/references/pbr-rules.md +193 -193
- package/plugins/pbr/references/plan-authoring.md +181 -181
- package/plugins/pbr/references/plan-format.md +287 -283
- package/plugins/pbr/references/planning-config.md +213 -213
- package/plugins/pbr/references/questioning.md +214 -214
- package/plugins/pbr/references/reading-verification.md +127 -127
- package/plugins/pbr/references/stub-patterns.md +160 -160
- package/plugins/pbr/references/subagent-coordination.md +119 -119
- package/plugins/pbr/references/ui-formatting.md +461 -399
- package/plugins/pbr/references/verification-patterns.md +198 -198
- package/plugins/pbr/references/wave-execution.md +95 -95
- package/plugins/pbr/scripts/auto-continue.js +80 -80
- package/plugins/pbr/scripts/check-dangerous-commands.js +136 -136
- package/plugins/pbr/scripts/check-doc-sprawl.js +102 -102
- package/plugins/pbr/scripts/check-phase-boundary.js +196 -196
- package/plugins/pbr/scripts/check-plan-format.js +270 -270
- package/plugins/pbr/scripts/check-roadmap-sync.js +322 -252
- package/plugins/pbr/scripts/check-skill-workflow.js +262 -262
- package/plugins/pbr/scripts/check-state-sync.js +476 -476
- package/plugins/pbr/scripts/check-subagent-output.js +144 -144
- package/plugins/pbr/scripts/config-schema.json +251 -251
- package/plugins/pbr/scripts/context-budget-check.js +287 -287
- package/plugins/pbr/scripts/event-handler.js +151 -151
- package/plugins/pbr/scripts/event-logger.js +92 -92
- package/plugins/pbr/scripts/hook-logger.js +80 -76
- package/plugins/pbr/scripts/hooks-schema.json +79 -79
- package/plugins/pbr/scripts/log-subagent.js +164 -152
- package/plugins/pbr/scripts/log-tool-failure.js +88 -88
- package/plugins/pbr/scripts/pbr-tools.js +1378 -1301
- package/plugins/pbr/scripts/post-write-dispatch.js +66 -66
- package/plugins/pbr/scripts/post-write-quality.js +207 -207
- package/plugins/pbr/scripts/pre-bash-dispatch.js +86 -56
- package/plugins/pbr/scripts/pre-write-dispatch.js +97 -62
- package/plugins/pbr/scripts/progress-tracker.js +281 -228
- package/plugins/pbr/scripts/run-hook.js +92 -0
- package/plugins/pbr/scripts/session-cleanup.js +254 -254
- package/plugins/pbr/scripts/status-line.js +288 -285
- package/plugins/pbr/scripts/suggest-compact.js +119 -119
- package/plugins/pbr/scripts/task-completed.js +45 -45
- package/plugins/pbr/scripts/track-context-budget.js +149 -119
- package/plugins/pbr/scripts/validate-commit.js +200 -200
- package/plugins/pbr/scripts/validate-plugin-structure.js +183 -172
- package/plugins/pbr/scripts/validate-task.js +106 -0
- package/plugins/pbr/skills/begin/SKILL.md +594 -545
- package/plugins/pbr/skills/begin/templates/PROJECT.md.tmpl +33 -33
- package/plugins/pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -18
- package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +49 -49
- package/plugins/pbr/skills/begin/templates/config.json.tmpl +64 -63
- package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +19 -19
- package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +30 -30
- package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +16 -16
- package/plugins/pbr/skills/build/SKILL.md +943 -962
- package/plugins/pbr/skills/config/SKILL.md +256 -241
- package/plugins/pbr/skills/continue/SKILL.md +164 -127
- package/plugins/pbr/skills/debug/SKILL.md +515 -489
- package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +16 -16
- package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +27 -27
- package/plugins/pbr/skills/discuss/SKILL.md +347 -338
- package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +61 -61
- package/plugins/pbr/skills/discuss/templates/decision-categories.md +9 -9
- package/plugins/pbr/skills/explore/SKILL.md +378 -362
- package/plugins/pbr/skills/health/SKILL.md +221 -186
- package/plugins/pbr/skills/health/templates/check-pattern.md.tmpl +30 -30
- package/plugins/pbr/skills/health/templates/output-format.md.tmpl +63 -63
- package/plugins/pbr/skills/help/SKILL.md +155 -140
- package/plugins/pbr/skills/import/SKILL.md +504 -490
- package/plugins/pbr/skills/milestone/SKILL.md +704 -673
- package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +48 -48
- package/plugins/pbr/skills/milestone/templates/stats-file.md.tmpl +30 -30
- package/plugins/pbr/skills/note/SKILL.md +231 -212
- package/plugins/pbr/skills/pause/SKILL.md +249 -235
- package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +71 -71
- package/plugins/pbr/skills/plan/SKILL.md +685 -628
- package/plugins/pbr/skills/plan/decimal-phase-calc.md +98 -98
- package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +21 -21
- package/plugins/pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -32
- package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +38 -38
- package/plugins/pbr/skills/plan/templates/researcher-prompt.md.tmpl +19 -19
- package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +23 -23
- package/plugins/pbr/skills/quick/SKILL.md +354 -335
- package/plugins/pbr/skills/resume/SKILL.md +402 -388
- package/plugins/pbr/skills/review/SKILL.md +686 -652
- package/plugins/pbr/skills/review/templates/debugger-prompt.md.tmpl +60 -60
- package/plugins/pbr/skills/review/templates/gap-planner-prompt.md.tmpl +40 -40
- package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +115 -115
- package/plugins/pbr/skills/scan/SKILL.md +304 -269
- package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +201 -201
- package/plugins/pbr/skills/setup/SKILL.md +253 -227
- package/plugins/pbr/skills/shared/commit-planning-docs.md +35 -35
- package/plugins/pbr/skills/shared/config-loading.md +102 -102
- package/plugins/pbr/skills/shared/context-budget.md +40 -40
- package/plugins/pbr/skills/shared/context-loader-task.md +86 -86
- package/plugins/pbr/skills/shared/digest-select.md +79 -79
- package/plugins/pbr/skills/shared/domain-probes.md +125 -125
- package/plugins/pbr/skills/shared/error-reporting.md +79 -79
- package/plugins/pbr/skills/shared/gate-prompts.md +388 -388
- package/plugins/pbr/skills/shared/phase-argument-parsing.md +45 -45
- package/plugins/pbr/skills/shared/progress-display.md +53 -53
- package/plugins/pbr/skills/shared/revision-loop.md +81 -81
- package/plugins/pbr/skills/shared/state-loading.md +62 -62
- package/plugins/pbr/skills/shared/state-update.md +161 -161
- package/plugins/pbr/skills/shared/universal-anti-patterns.md +33 -33
- package/plugins/pbr/skills/status/SKILL.md +367 -353
- package/plugins/pbr/skills/todo/SKILL.md +198 -181
- package/plugins/pbr/templates/CONTEXT.md.tmpl +52 -52
- package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +151 -151
- package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +97 -97
- package/plugins/pbr/templates/ROADMAP.md.tmpl +40 -40
- package/plugins/pbr/templates/SUMMARY.md.tmpl +81 -81
- package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +116 -116
- package/plugins/pbr/templates/codebase/ARCHITECTURE.md.tmpl +98 -98
- package/plugins/pbr/templates/codebase/CONCERNS.md.tmpl +93 -93
- package/plugins/pbr/templates/codebase/CONVENTIONS.md.tmpl +104 -104
- package/plugins/pbr/templates/codebase/INTEGRATIONS.md.tmpl +78 -78
- package/plugins/pbr/templates/codebase/STACK.md.tmpl +78 -78
- package/plugins/pbr/templates/codebase/STRUCTURE.md.tmpl +80 -80
- package/plugins/pbr/templates/codebase/TESTING.md.tmpl +107 -107
- package/plugins/pbr/templates/continue-here.md.tmpl +73 -73
- package/plugins/pbr/templates/prompt-partials/phase-project-context.md.tmpl +37 -37
- package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +124 -124
- package/plugins/pbr/templates/research/STACK.md.tmpl +71 -71
- package/plugins/pbr/templates/research/SUMMARY.md.tmpl +112 -112
- package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +81 -81
- package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +99 -99
- 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
|
-
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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(); }
|