@sienklogic/plan-build-run 2.0.0 → 2.0.2
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/cursor-pbr/.cursor-plugin/plugin.json +22 -0
- package/plugins/cursor-pbr/agents/.gitkeep +0 -0
- package/plugins/cursor-pbr/assets/.gitkeep +0 -0
- package/plugins/cursor-pbr/hooks/hooks.json +11 -0
- package/plugins/cursor-pbr/references/.gitkeep +0 -0
- package/plugins/cursor-pbr/rules/.gitkeep +0 -0
- package/plugins/cursor-pbr/skills/.gitkeep +0 -0
- package/plugins/cursor-pbr/templates/.gitkeep +0 -0
- 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,228 +1,281 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SessionStart hook: Auto-detects .planning/ directory and injects
|
|
5
|
-
* project state as additionalContext.
|
|
6
|
-
*
|
|
7
|
-
* If no .planning/ directory exists, exits silently (non-Plan-Build-Run project).
|
|
8
|
-
* If STATE.md exists, reads and outputs a concise summary.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const os = require('os');
|
|
14
|
-
const { execSync } = require('child_process');
|
|
15
|
-
const { logHook } = require('./hook-logger');
|
|
16
|
-
const { logEvent } = require('./event-logger');
|
|
17
|
-
const { configLoad } = require('./pbr-tools');
|
|
18
|
-
|
|
19
|
-
function main() {
|
|
20
|
-
const cwd = process.cwd();
|
|
21
|
-
const planningDir = path.join(cwd, '.planning');
|
|
22
|
-
const stateFile = path.join(planningDir, 'STATE.md');
|
|
23
|
-
|
|
24
|
-
// Not a Plan-Build-Run project
|
|
25
|
-
if (!fs.existsSync(planningDir)) {
|
|
26
|
-
process.exit(0);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Reset compaction counter for new session
|
|
30
|
-
const { resetCounter } = require('./suggest-compact');
|
|
31
|
-
resetCounter(planningDir);
|
|
32
|
-
|
|
33
|
-
const context = buildContext(planningDir, stateFile);
|
|
34
|
-
|
|
35
|
-
if (context) {
|
|
36
|
-
const output = {
|
|
37
|
-
additionalContext: context
|
|
38
|
-
};
|
|
39
|
-
process.stdout.write(JSON.stringify(output));
|
|
40
|
-
logHook('progress-tracker', 'SessionStart', 'injected', { hasState: true });
|
|
41
|
-
logEvent('workflow', 'session-start', { hasState: true });
|
|
42
|
-
} else {
|
|
43
|
-
logHook('progress-tracker', 'SessionStart', 'skipped', { hasState: false });
|
|
44
|
-
logEvent('workflow', 'session-start', { hasState: false });
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
process.exit(0);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function buildContext(planningDir, stateFile) {
|
|
51
|
-
const parts = [];
|
|
52
|
-
|
|
53
|
-
parts.push('[Plan-Build-Run Project Detected]');
|
|
54
|
-
|
|
55
|
-
// Git context
|
|
56
|
-
try {
|
|
57
|
-
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
58
|
-
const porcelain = execSync('git status --porcelain', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
59
|
-
const uncommitted = porcelain ? porcelain.split('\n').length : 0;
|
|
60
|
-
const recentCommits = execSync('git log -5 --oneline', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
61
|
-
parts.push(`\nGit: ${branch} (${uncommitted} uncommitted file${uncommitted !== 1 ? 's' : ''})`);
|
|
62
|
-
if (recentCommits) {
|
|
63
|
-
parts.push(`Recent commits:\n${recentCommits}`);
|
|
64
|
-
}
|
|
65
|
-
} catch (_e) {
|
|
66
|
-
// Not a git repo or git not available — skip
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Read STATE.md if it exists
|
|
70
|
-
if (fs.existsSync(stateFile)) {
|
|
71
|
-
const state = fs.readFileSync(stateFile, 'utf8');
|
|
72
|
-
|
|
73
|
-
// Extract key sections
|
|
74
|
-
const position = extractSection(state, 'Current Position');
|
|
75
|
-
if (position) {
|
|
76
|
-
parts.push(`\nPosition:\n${position}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const blockers = extractSection(state, 'Blockers/Concerns');
|
|
80
|
-
if (blockers && !blockers.includes('None')) {
|
|
81
|
-
parts.push(`\nBlockers:\n${blockers}`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const continuity = extractSection(state, 'Session Continuity');
|
|
85
|
-
if (continuity) {
|
|
86
|
-
parts.push(`\nLast Session:\n${continuity}`);
|
|
87
|
-
}
|
|
88
|
-
} else {
|
|
89
|
-
parts.push('\nNo STATE.md found. Run /pbr:begin to initialize or /pbr:status to check.');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Check for .continue-here.md files
|
|
93
|
-
const phasesDir = path.join(planningDir, 'phases');
|
|
94
|
-
if (fs.existsSync(phasesDir)) {
|
|
95
|
-
const continueFiles = findContinueFiles(phasesDir);
|
|
96
|
-
if (continueFiles.length > 0) {
|
|
97
|
-
parts.push(`\nPaused work found: ${continueFiles.join(', ')}`);
|
|
98
|
-
parts.push('Run /pbr:resume to pick up where you left off.');
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Check for config and validate
|
|
103
|
-
const config = configLoad(planningDir);
|
|
104
|
-
if (config) {
|
|
105
|
-
parts.push(`\nConfig: depth=${config.depth || 'standard'}, mode=${config.mode || 'interactive'}`);
|
|
106
|
-
|
|
107
|
-
// Validate config against schema (reuse already-loaded config)
|
|
108
|
-
const schemaPath = path.join(__dirname, 'config-schema.json');
|
|
109
|
-
if (fs.existsSync(schemaPath)) {
|
|
110
|
-
const { configValidate } = require('./pbr-tools');
|
|
111
|
-
const validation = configValidate(config);
|
|
112
|
-
if (validation.warnings.length > 0) {
|
|
113
|
-
parts.push(`\nConfig warnings: ${validation.warnings.join('; ')}`);
|
|
114
|
-
}
|
|
115
|
-
if (validation.errors.length > 0) {
|
|
116
|
-
parts.push(`\nConfig errors: ${validation.errors.join('; ')}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Check for quick notes
|
|
122
|
-
const projectNotesFile = path.join(planningDir, 'NOTES.md');
|
|
123
|
-
const globalNotesFile = path.join(os.homedir(), '.claude', 'notes.md');
|
|
124
|
-
const projectNoteCount = countNotes(projectNotesFile);
|
|
125
|
-
const globalNoteCount = countNotes(globalNotesFile);
|
|
126
|
-
if (projectNoteCount > 0 || globalNoteCount > 0) {
|
|
127
|
-
const noteParts = [];
|
|
128
|
-
if (projectNoteCount > 0) noteParts.push(`${projectNoteCount} project`);
|
|
129
|
-
if (globalNoteCount > 0) noteParts.push(`${globalNoteCount} global`);
|
|
130
|
-
parts.push(`\nNotes: ${noteParts.join(', ')}. \`/pbr:note list\` to review.`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Check ROADMAP/STATE sync (S>M-2)
|
|
134
|
-
const roadmapFile = path.join(planningDir, 'ROADMAP.md');
|
|
135
|
-
if (fs.existsSync(stateFile) && fs.existsSync(roadmapFile)) {
|
|
136
|
-
try {
|
|
137
|
-
const roadmap = fs.readFileSync(roadmapFile, 'utf8');
|
|
138
|
-
const state = fs.readFileSync(stateFile, 'utf8');
|
|
139
|
-
|
|
140
|
-
// Extract current phase from STATE.md
|
|
141
|
-
const phaseMatch = state.match(/Phase:\s*(\d+)\s+of\s+\d+/);
|
|
142
|
-
if (phaseMatch) {
|
|
143
|
-
const currentPhase = parseInt(phaseMatch[1], 10);
|
|
144
|
-
// Check if ROADMAP shows this phase as already verified/complete
|
|
145
|
-
const progressTable = roadmap.match(/## Progress[\s\S]*?\|[\s\S]*?(?=\n##|\s*$)/);
|
|
146
|
-
if (progressTable) {
|
|
147
|
-
const rows = progressTable[0].split('\n').filter(r => r.includes('|'));
|
|
148
|
-
for (const row of rows) {
|
|
149
|
-
const cols = row.split('|').map(c => c.trim()).filter(Boolean);
|
|
150
|
-
if (cols.length >= 4) {
|
|
151
|
-
const phaseNum = parseInt(cols[0], 10);
|
|
152
|
-
const status = cols[3] ? cols[3].toLowerCase() : '';
|
|
153
|
-
if (phaseNum === currentPhase && (status === 'verified' || status === 'complete')) {
|
|
154
|
-
parts.push(`\nWarning: STATE.md may be outdated — ROADMAP.md shows phase ${currentPhase} as ${status}.`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
} catch (_e) {
|
|
161
|
-
// Ignore parse errors
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Check for stale .auto-next signal (S>M-9)
|
|
166
|
-
const autoNextFile = path.join(planningDir, '.auto-next');
|
|
167
|
-
if (fs.existsSync(autoNextFile)) {
|
|
168
|
-
try {
|
|
169
|
-
const stats = fs.statSync(autoNextFile);
|
|
170
|
-
const ageMs = Date.now() - stats.mtimeMs;
|
|
171
|
-
const ageMinutes = Math.floor(ageMs / 60000);
|
|
172
|
-
if (ageMinutes > 10) {
|
|
173
|
-
parts.push(`\nWarning: Stale .auto-next signal found (${ageMinutes} minutes old). This may trigger an unexpected command. Consider deleting .planning/.auto-next.`);
|
|
174
|
-
logHook('progress-tracker', 'SessionStart', 'stale-auto-next', { ageMinutes });
|
|
175
|
-
}
|
|
176
|
-
} catch (_e) {
|
|
177
|
-
// Ignore errors
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SessionStart hook: Auto-detects .planning/ directory and injects
|
|
5
|
+
* project state as additionalContext.
|
|
6
|
+
*
|
|
7
|
+
* If no .planning/ directory exists, exits silently (non-Plan-Build-Run project).
|
|
8
|
+
* If STATE.md exists, reads and outputs a concise summary.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
const { logHook } = require('./hook-logger');
|
|
16
|
+
const { logEvent } = require('./event-logger');
|
|
17
|
+
const { configLoad } = require('./pbr-tools');
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
const cwd = process.cwd();
|
|
21
|
+
const planningDir = path.join(cwd, '.planning');
|
|
22
|
+
const stateFile = path.join(planningDir, 'STATE.md');
|
|
23
|
+
|
|
24
|
+
// Not a Plan-Build-Run project
|
|
25
|
+
if (!fs.existsSync(planningDir)) {
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Reset compaction counter for new session
|
|
30
|
+
const { resetCounter } = require('./suggest-compact');
|
|
31
|
+
resetCounter(planningDir);
|
|
32
|
+
|
|
33
|
+
const context = buildContext(planningDir, stateFile);
|
|
34
|
+
|
|
35
|
+
if (context) {
|
|
36
|
+
const output = {
|
|
37
|
+
additionalContext: context
|
|
38
|
+
};
|
|
39
|
+
process.stdout.write(JSON.stringify(output));
|
|
40
|
+
logHook('progress-tracker', 'SessionStart', 'injected', { hasState: true });
|
|
41
|
+
logEvent('workflow', 'session-start', { hasState: true });
|
|
42
|
+
} else {
|
|
43
|
+
logHook('progress-tracker', 'SessionStart', 'skipped', { hasState: false });
|
|
44
|
+
logEvent('workflow', 'session-start', { hasState: false });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildContext(planningDir, stateFile) {
|
|
51
|
+
const parts = [];
|
|
52
|
+
|
|
53
|
+
parts.push('[Plan-Build-Run Project Detected]');
|
|
54
|
+
|
|
55
|
+
// Git context
|
|
56
|
+
try {
|
|
57
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
58
|
+
const porcelain = execSync('git status --porcelain', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
59
|
+
const uncommitted = porcelain ? porcelain.split('\n').length : 0;
|
|
60
|
+
const recentCommits = execSync('git log -5 --oneline', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
61
|
+
parts.push(`\nGit: ${branch} (${uncommitted} uncommitted file${uncommitted !== 1 ? 's' : ''})`);
|
|
62
|
+
if (recentCommits) {
|
|
63
|
+
parts.push(`Recent commits:\n${recentCommits}`);
|
|
64
|
+
}
|
|
65
|
+
} catch (_e) {
|
|
66
|
+
// Not a git repo or git not available — skip
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Read STATE.md if it exists
|
|
70
|
+
if (fs.existsSync(stateFile)) {
|
|
71
|
+
const state = fs.readFileSync(stateFile, 'utf8');
|
|
72
|
+
|
|
73
|
+
// Extract key sections
|
|
74
|
+
const position = extractSection(state, 'Current Position');
|
|
75
|
+
if (position) {
|
|
76
|
+
parts.push(`\nPosition:\n${position}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const blockers = extractSection(state, 'Blockers/Concerns');
|
|
80
|
+
if (blockers && !blockers.includes('None')) {
|
|
81
|
+
parts.push(`\nBlockers:\n${blockers}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const continuity = extractSection(state, 'Session Continuity');
|
|
85
|
+
if (continuity) {
|
|
86
|
+
parts.push(`\nLast Session:\n${continuity}`);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
parts.push('\nNo STATE.md found. Run /pbr:begin to initialize or /pbr:status to check.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check for .continue-here.md files
|
|
93
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
94
|
+
if (fs.existsSync(phasesDir)) {
|
|
95
|
+
const continueFiles = findContinueFiles(phasesDir);
|
|
96
|
+
if (continueFiles.length > 0) {
|
|
97
|
+
parts.push(`\nPaused work found: ${continueFiles.join(', ')}`);
|
|
98
|
+
parts.push('Run /pbr:resume to pick up where you left off.');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check for config and validate
|
|
103
|
+
const config = configLoad(planningDir);
|
|
104
|
+
if (config) {
|
|
105
|
+
parts.push(`\nConfig: depth=${config.depth || 'standard'}, mode=${config.mode || 'interactive'}`);
|
|
106
|
+
|
|
107
|
+
// Validate config against schema (reuse already-loaded config)
|
|
108
|
+
const schemaPath = path.join(__dirname, 'config-schema.json');
|
|
109
|
+
if (fs.existsSync(schemaPath)) {
|
|
110
|
+
const { configValidate } = require('./pbr-tools');
|
|
111
|
+
const validation = configValidate(config);
|
|
112
|
+
if (validation.warnings.length > 0) {
|
|
113
|
+
parts.push(`\nConfig warnings: ${validation.warnings.join('; ')}`);
|
|
114
|
+
}
|
|
115
|
+
if (validation.errors.length > 0) {
|
|
116
|
+
parts.push(`\nConfig errors: ${validation.errors.join('; ')}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check for quick notes
|
|
122
|
+
const projectNotesFile = path.join(planningDir, 'NOTES.md');
|
|
123
|
+
const globalNotesFile = path.join(os.homedir(), '.claude', 'notes.md');
|
|
124
|
+
const projectNoteCount = countNotes(projectNotesFile);
|
|
125
|
+
const globalNoteCount = countNotes(globalNotesFile);
|
|
126
|
+
if (projectNoteCount > 0 || globalNoteCount > 0) {
|
|
127
|
+
const noteParts = [];
|
|
128
|
+
if (projectNoteCount > 0) noteParts.push(`${projectNoteCount} project`);
|
|
129
|
+
if (globalNoteCount > 0) noteParts.push(`${globalNoteCount} global`);
|
|
130
|
+
parts.push(`\nNotes: ${noteParts.join(', ')}. \`/pbr:note list\` to review.`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check ROADMAP/STATE sync (S>M-2)
|
|
134
|
+
const roadmapFile = path.join(planningDir, 'ROADMAP.md');
|
|
135
|
+
if (fs.existsSync(stateFile) && fs.existsSync(roadmapFile)) {
|
|
136
|
+
try {
|
|
137
|
+
const roadmap = fs.readFileSync(roadmapFile, 'utf8');
|
|
138
|
+
const state = fs.readFileSync(stateFile, 'utf8');
|
|
139
|
+
|
|
140
|
+
// Extract current phase from STATE.md
|
|
141
|
+
const phaseMatch = state.match(/Phase:\s*(\d+)\s+of\s+\d+/);
|
|
142
|
+
if (phaseMatch) {
|
|
143
|
+
const currentPhase = parseInt(phaseMatch[1], 10);
|
|
144
|
+
// Check if ROADMAP shows this phase as already verified/complete
|
|
145
|
+
const progressTable = roadmap.match(/## Progress[\s\S]*?\|[\s\S]*?(?=\n##|\s*$)/);
|
|
146
|
+
if (progressTable) {
|
|
147
|
+
const rows = progressTable[0].split('\n').filter(r => r.includes('|'));
|
|
148
|
+
for (const row of rows) {
|
|
149
|
+
const cols = row.split('|').map(c => c.trim()).filter(Boolean);
|
|
150
|
+
if (cols.length >= 4) {
|
|
151
|
+
const phaseNum = parseInt(cols[0], 10);
|
|
152
|
+
const status = cols[3] ? cols[3].toLowerCase() : '';
|
|
153
|
+
if (phaseNum === currentPhase && (status === 'verified' || status === 'complete')) {
|
|
154
|
+
parts.push(`\nWarning: STATE.md may be outdated — ROADMAP.md shows phase ${currentPhase} as ${status}.`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch (_e) {
|
|
161
|
+
// Ignore parse errors
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check for stale .auto-next signal (S>M-9)
|
|
166
|
+
const autoNextFile = path.join(planningDir, '.auto-next');
|
|
167
|
+
if (fs.existsSync(autoNextFile)) {
|
|
168
|
+
try {
|
|
169
|
+
const stats = fs.statSync(autoNextFile);
|
|
170
|
+
const ageMs = Date.now() - stats.mtimeMs;
|
|
171
|
+
const ageMinutes = Math.floor(ageMs / 60000);
|
|
172
|
+
if (ageMinutes > 10) {
|
|
173
|
+
parts.push(`\nWarning: Stale .auto-next signal found (${ageMinutes} minutes old). This may trigger an unexpected command. Consider deleting .planning/.auto-next.`);
|
|
174
|
+
logHook('progress-tracker', 'SessionStart', 'stale-auto-next', { ageMinutes });
|
|
175
|
+
}
|
|
176
|
+
} catch (_e) {
|
|
177
|
+
// Ignore errors
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Hook health summary from recent log entries
|
|
182
|
+
const hookHealth = getHookHealthSummary(planningDir);
|
|
183
|
+
if (hookHealth) {
|
|
184
|
+
parts.push(`\n${hookHealth}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
parts.push('\nAvailable commands: /pbr:status, /pbr:plan, /pbr:build, /pbr:review, /pbr:help');
|
|
188
|
+
|
|
189
|
+
return parts.join('\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function extractSection(content, heading) {
|
|
193
|
+
const regex = new RegExp(`##\\s+${escapeRegex(heading)}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`);
|
|
194
|
+
const match = content.match(regex);
|
|
195
|
+
if (!match) return null;
|
|
196
|
+
const section = match[1].trim();
|
|
197
|
+
// Return first 5 lines max
|
|
198
|
+
return section.split('\n').slice(0, 5).join('\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function escapeRegex(str) {
|
|
202
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function findContinueFiles(dir) {
|
|
206
|
+
const results = [];
|
|
207
|
+
try {
|
|
208
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
const fullPath = path.join(dir, entry.name);
|
|
211
|
+
if (entry.isDirectory()) {
|
|
212
|
+
results.push(...findContinueFiles(fullPath));
|
|
213
|
+
} else if (entry.name.includes('.continue-here')) {
|
|
214
|
+
results.push(path.relative(dir, fullPath));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch (_e) {
|
|
218
|
+
// Ignore permission errors
|
|
219
|
+
}
|
|
220
|
+
return results;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function countNotes(filePath) {
|
|
224
|
+
try {
|
|
225
|
+
if (!fs.existsSync(filePath)) return 0;
|
|
226
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
227
|
+
const lines = content.split('\n');
|
|
228
|
+
return lines.filter(l => /^- \[/.test(l) && !l.includes('[promoted]')).length;
|
|
229
|
+
} catch (_e) {
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const FAILURE_DECISIONS = /^(block|error|warn|warning|block-coauthor|block-sensitive|unlink-failed)$/;
|
|
235
|
+
const HOOK_HEALTH_MAX_ENTRIES = 50;
|
|
236
|
+
|
|
237
|
+
function getHookHealthSummary(planningDir) {
|
|
238
|
+
const logPath = path.join(planningDir, 'logs', 'hooks.jsonl');
|
|
239
|
+
try {
|
|
240
|
+
if (!fs.existsSync(logPath)) return null;
|
|
241
|
+
const content = fs.readFileSync(logPath, 'utf8').trim();
|
|
242
|
+
if (!content) return null;
|
|
243
|
+
|
|
244
|
+
const lines = content.split('\n');
|
|
245
|
+
// Take only the last N entries
|
|
246
|
+
const recent = lines.slice(-HOOK_HEALTH_MAX_ENTRIES);
|
|
247
|
+
|
|
248
|
+
const failuresByHook = {};
|
|
249
|
+
let totalFailures = 0;
|
|
250
|
+
|
|
251
|
+
for (const line of recent) {
|
|
252
|
+
try {
|
|
253
|
+
const entry = JSON.parse(line);
|
|
254
|
+
if (entry.decision && FAILURE_DECISIONS.test(entry.decision)) {
|
|
255
|
+
const hookName = entry.hook || 'unknown';
|
|
256
|
+
failuresByHook[hookName] = (failuresByHook[hookName] || 0) + 1;
|
|
257
|
+
totalFailures++;
|
|
258
|
+
}
|
|
259
|
+
} catch (_e) {
|
|
260
|
+
// Skip malformed lines
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (totalFailures === 0) return null;
|
|
265
|
+
|
|
266
|
+
// Sort hooks by failure count descending
|
|
267
|
+
const sorted = Object.entries(failuresByHook)
|
|
268
|
+
.sort((a, b) => b[1] - a[1])
|
|
269
|
+
.map(([hook, count]) => `${hook}: ${count}`)
|
|
270
|
+
.join(', ');
|
|
271
|
+
|
|
272
|
+
return `Hook health: ${totalFailures} failure${totalFailures !== 1 ? 's' : ''} in last ${recent.length} entries (${sorted})`;
|
|
273
|
+
} catch (_e) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Exported for testing
|
|
279
|
+
module.exports = { getHookHealthSummary, FAILURE_DECISIONS, HOOK_HEALTH_MAX_ENTRIES };
|
|
280
|
+
|
|
281
|
+
main();
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook wrapper that normalizes CLAUDE_PLUGIN_ROOT paths on Windows.
|
|
5
|
+
*
|
|
6
|
+
* Problem: On Windows with Git Bash, ${CLAUDE_PLUGIN_ROOT} expands to
|
|
7
|
+
* a MSYS-style path like /d/Repos/project/plugins/pbr. Node.js on Windows
|
|
8
|
+
* interprets this as D:\d\Repos\... (relative to drive root),
|
|
9
|
+
* causing MODULE_NOT_FOUND errors.
|
|
10
|
+
*
|
|
11
|
+
* Invocation from hooks.json (bootstrap pattern):
|
|
12
|
+
* "node -e \"require(require('path').resolve(
|
|
13
|
+
* (function(r){var m=r.match(/^\\/([a-zA-Z])\\/(.*)/);
|
|
14
|
+
* return m?m[1]+':'+m[2].replace(/\\//g,'\\\\'):r})
|
|
15
|
+
* (process.env.CLAUDE_PLUGIN_ROOT||''),
|
|
16
|
+
* 'scripts','run-hook.js'))(process.argv[1])\" <script> [args...]"
|
|
17
|
+
*
|
|
18
|
+
* Or directly (when CLAUDE_PLUGIN_ROOT resolves correctly):
|
|
19
|
+
* "node ${CLAUDE_PLUGIN_ROOT}/scripts/run-hook.js <script-name> [args...]"
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Fix MSYS-style paths on Windows.
|
|
28
|
+
* Converts /d/Repos/... to D:\Repos\...
|
|
29
|
+
*/
|
|
30
|
+
function fixMsysPath(p) {
|
|
31
|
+
if (!p) return p;
|
|
32
|
+
const match = p.match(/^\/([a-zA-Z])\/(.*)/);
|
|
33
|
+
if (match) {
|
|
34
|
+
return match[1].toUpperCase() + ':\\' + match[2].replace(/\//g, '\\');
|
|
35
|
+
}
|
|
36
|
+
return p;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Fix CLAUDE_PLUGIN_ROOT in environment
|
|
40
|
+
const pluginRoot = fixMsysPath(process.env.CLAUDE_PLUGIN_ROOT || '');
|
|
41
|
+
|
|
42
|
+
// When invoked via `node -e "..." scriptName`, process.argv is:
|
|
43
|
+
// [node, scriptName, ...extra]
|
|
44
|
+
// When invoked via `node run-hook.js scriptName`, process.argv is:
|
|
45
|
+
// [node, run-hook.js, scriptName, ...extra]
|
|
46
|
+
// Detect which case we're in:
|
|
47
|
+
const invokedViaEval = !process.argv[1] ||
|
|
48
|
+
!process.argv[1].endsWith('run-hook.js');
|
|
49
|
+
|
|
50
|
+
let scriptName, scriptArgs;
|
|
51
|
+
if (invokedViaEval) {
|
|
52
|
+
// Called as module: exports a function, or check argv[1]
|
|
53
|
+
scriptName = process.argv[1] || null;
|
|
54
|
+
scriptArgs = process.argv.slice(2);
|
|
55
|
+
} else {
|
|
56
|
+
// Called directly: node run-hook.js <script> [args...]
|
|
57
|
+
scriptName = process.argv[2] || null;
|
|
58
|
+
scriptArgs = process.argv.slice(3);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// When required as a module from -e bootstrap, export a runner function
|
|
62
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
63
|
+
module.exports = runScript;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// If we have a script name, run it immediately
|
|
67
|
+
if (scriptName) {
|
|
68
|
+
runScript(scriptName, scriptArgs);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function runScript(name, args) {
|
|
72
|
+
args = args || [];
|
|
73
|
+
// Try __dirname first, then pluginRoot
|
|
74
|
+
const candidates = [
|
|
75
|
+
path.resolve(__dirname, name),
|
|
76
|
+
pluginRoot ? path.resolve(pluginRoot, 'scripts', name) : null
|
|
77
|
+
].filter(Boolean);
|
|
78
|
+
|
|
79
|
+
for (const candidate of candidates) {
|
|
80
|
+
try {
|
|
81
|
+
process.argv = [process.argv[0], candidate, ...args];
|
|
82
|
+
require(candidate);
|
|
83
|
+
return;
|
|
84
|
+
} catch (err) {
|
|
85
|
+
if (err.code !== 'MODULE_NOT_FOUND') throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
process.stderr.write(`run-hook: cannot find script: ${name}\n`);
|
|
90
|
+
process.stderr.write(` searched: ${candidates.join(', ')}\n`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|