@sienklogic/plan-build-run 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/CLAUDE.md +149 -0
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/dashboard/bin/cli.js +25 -0
- package/dashboard/package.json +34 -0
- package/dashboard/public/.gitkeep +0 -0
- package/dashboard/public/css/layout.css +406 -0
- package/dashboard/public/css/status-colors.css +98 -0
- package/dashboard/public/js/htmx-title.js +5 -0
- package/dashboard/public/js/sidebar-toggle.js +20 -0
- package/dashboard/src/app.js +78 -0
- package/dashboard/src/middleware/errorHandler.js +52 -0
- package/dashboard/src/middleware/notFoundHandler.js +9 -0
- package/dashboard/src/repositories/planning.repository.js +128 -0
- package/dashboard/src/routes/events.routes.js +40 -0
- package/dashboard/src/routes/index.routes.js +31 -0
- package/dashboard/src/routes/pages.routes.js +195 -0
- package/dashboard/src/server.js +42 -0
- package/dashboard/src/services/dashboard.service.js +222 -0
- package/dashboard/src/services/phase.service.js +167 -0
- package/dashboard/src/services/project.service.js +57 -0
- package/dashboard/src/services/roadmap.service.js +171 -0
- package/dashboard/src/services/sse.service.js +58 -0
- package/dashboard/src/services/todo.service.js +254 -0
- package/dashboard/src/services/watcher.service.js +48 -0
- package/dashboard/src/views/coming-soon.ejs +11 -0
- package/dashboard/src/views/error.ejs +13 -0
- package/dashboard/src/views/index.ejs +5 -0
- package/dashboard/src/views/layout.ejs +1 -0
- package/dashboard/src/views/partials/dashboard-content.ejs +77 -0
- package/dashboard/src/views/partials/footer.ejs +3 -0
- package/dashboard/src/views/partials/head.ejs +21 -0
- package/dashboard/src/views/partials/header.ejs +12 -0
- package/dashboard/src/views/partials/layout-bottom.ejs +15 -0
- package/dashboard/src/views/partials/layout-top.ejs +8 -0
- package/dashboard/src/views/partials/phase-content.ejs +181 -0
- package/dashboard/src/views/partials/phases-content.ejs +117 -0
- package/dashboard/src/views/partials/roadmap-content.ejs +142 -0
- package/dashboard/src/views/partials/sidebar.ejs +38 -0
- package/dashboard/src/views/partials/todo-create-content.ejs +53 -0
- package/dashboard/src/views/partials/todo-detail-content.ejs +38 -0
- package/dashboard/src/views/partials/todos-content.ejs +53 -0
- package/dashboard/src/views/phase-detail.ejs +5 -0
- package/dashboard/src/views/phases.ejs +5 -0
- package/dashboard/src/views/roadmap.ejs +5 -0
- package/dashboard/src/views/todo-create.ejs +5 -0
- package/dashboard/src/views/todo-detail.ejs +5 -0
- package/dashboard/src/views/todos.ejs +5 -0
- package/package.json +57 -0
- package/plugins/pbr/.claude-plugin/plugin.json +13 -0
- package/plugins/pbr/UI-CONSISTENCY-GAPS.md +61 -0
- package/plugins/pbr/agents/codebase-mapper.md +271 -0
- package/plugins/pbr/agents/debugger.md +281 -0
- package/plugins/pbr/agents/executor.md +407 -0
- package/plugins/pbr/agents/general.md +164 -0
- package/plugins/pbr/agents/integration-checker.md +141 -0
- package/plugins/pbr/agents/plan-checker.md +280 -0
- package/plugins/pbr/agents/planner.md +358 -0
- package/plugins/pbr/agents/researcher.md +363 -0
- package/plugins/pbr/agents/synthesizer.md +230 -0
- package/plugins/pbr/agents/verifier.md +454 -0
- package/plugins/pbr/commands/begin.md +5 -0
- package/plugins/pbr/commands/build.md +5 -0
- package/plugins/pbr/commands/config.md +5 -0
- package/plugins/pbr/commands/continue.md +5 -0
- package/plugins/pbr/commands/debug.md +5 -0
- package/plugins/pbr/commands/discuss.md +5 -0
- package/plugins/pbr/commands/explore.md +5 -0
- package/plugins/pbr/commands/health.md +5 -0
- package/plugins/pbr/commands/help.md +5 -0
- package/plugins/pbr/commands/import.md +5 -0
- package/plugins/pbr/commands/milestone.md +5 -0
- package/plugins/pbr/commands/note.md +5 -0
- package/plugins/pbr/commands/pause.md +5 -0
- package/plugins/pbr/commands/plan.md +5 -0
- package/plugins/pbr/commands/quick.md +5 -0
- package/plugins/pbr/commands/resume.md +5 -0
- package/plugins/pbr/commands/review.md +5 -0
- package/plugins/pbr/commands/scan.md +5 -0
- package/plugins/pbr/commands/setup.md +5 -0
- package/plugins/pbr/commands/status.md +5 -0
- package/plugins/pbr/commands/todo.md +5 -0
- package/plugins/pbr/contexts/dev.md +27 -0
- package/plugins/pbr/contexts/research.md +28 -0
- package/plugins/pbr/contexts/review.md +36 -0
- package/plugins/pbr/hooks/hooks.json +183 -0
- package/plugins/pbr/references/agent-anti-patterns.md +24 -0
- package/plugins/pbr/references/agent-interactions.md +134 -0
- package/plugins/pbr/references/agent-teams.md +54 -0
- package/plugins/pbr/references/checkpoints.md +157 -0
- package/plugins/pbr/references/common-bug-patterns.md +13 -0
- package/plugins/pbr/references/continuation-format.md +212 -0
- package/plugins/pbr/references/deviation-rules.md +112 -0
- package/plugins/pbr/references/git-integration.md +226 -0
- package/plugins/pbr/references/integration-patterns.md +117 -0
- package/plugins/pbr/references/model-profiles.md +99 -0
- package/plugins/pbr/references/model-selection.md +31 -0
- package/plugins/pbr/references/pbr-rules.md +193 -0
- package/plugins/pbr/references/plan-authoring.md +181 -0
- package/plugins/pbr/references/plan-format.md +283 -0
- package/plugins/pbr/references/planning-config.md +213 -0
- package/plugins/pbr/references/questioning.md +214 -0
- package/plugins/pbr/references/reading-verification.md +127 -0
- package/plugins/pbr/references/stub-patterns.md +160 -0
- package/plugins/pbr/references/subagent-coordination.md +119 -0
- package/plugins/pbr/references/ui-formatting.md +399 -0
- package/plugins/pbr/references/verification-patterns.md +198 -0
- package/plugins/pbr/references/wave-execution.md +95 -0
- package/plugins/pbr/scripts/auto-continue.js +80 -0
- package/plugins/pbr/scripts/check-dangerous-commands.js +136 -0
- package/plugins/pbr/scripts/check-doc-sprawl.js +102 -0
- package/plugins/pbr/scripts/check-phase-boundary.js +196 -0
- package/plugins/pbr/scripts/check-plan-format.js +270 -0
- package/plugins/pbr/scripts/check-roadmap-sync.js +252 -0
- package/plugins/pbr/scripts/check-skill-workflow.js +262 -0
- package/plugins/pbr/scripts/check-state-sync.js +476 -0
- package/plugins/pbr/scripts/check-subagent-output.js +144 -0
- package/plugins/pbr/scripts/config-schema.json +251 -0
- package/plugins/pbr/scripts/context-budget-check.js +287 -0
- package/plugins/pbr/scripts/event-handler.js +151 -0
- package/plugins/pbr/scripts/event-logger.js +92 -0
- package/plugins/pbr/scripts/hook-logger.js +76 -0
- package/plugins/pbr/scripts/hooks-schema.json +79 -0
- package/plugins/pbr/scripts/log-subagent.js +152 -0
- package/plugins/pbr/scripts/log-tool-failure.js +88 -0
- package/plugins/pbr/scripts/pbr-tools.js +1301 -0
- package/plugins/pbr/scripts/post-write-dispatch.js +66 -0
- package/plugins/pbr/scripts/post-write-quality.js +207 -0
- package/plugins/pbr/scripts/pre-bash-dispatch.js +56 -0
- package/plugins/pbr/scripts/pre-write-dispatch.js +62 -0
- package/plugins/pbr/scripts/progress-tracker.js +228 -0
- package/plugins/pbr/scripts/session-cleanup.js +254 -0
- package/plugins/pbr/scripts/status-line.js +285 -0
- package/plugins/pbr/scripts/suggest-compact.js +119 -0
- package/plugins/pbr/scripts/task-completed.js +45 -0
- package/plugins/pbr/scripts/track-context-budget.js +119 -0
- package/plugins/pbr/scripts/validate-commit.js +200 -0
- package/plugins/pbr/scripts/validate-plugin-structure.js +172 -0
- package/plugins/pbr/skills/begin/SKILL.md +545 -0
- package/plugins/pbr/skills/begin/templates/PROJECT.md.tmpl +33 -0
- package/plugins/pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -0
- package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +49 -0
- package/plugins/pbr/skills/begin/templates/config.json.tmpl +63 -0
- package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +19 -0
- package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +30 -0
- package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +16 -0
- package/plugins/pbr/skills/build/SKILL.md +962 -0
- package/plugins/pbr/skills/config/SKILL.md +241 -0
- package/plugins/pbr/skills/continue/SKILL.md +127 -0
- package/plugins/pbr/skills/debug/SKILL.md +489 -0
- package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +16 -0
- package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +27 -0
- package/plugins/pbr/skills/discuss/SKILL.md +338 -0
- package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +61 -0
- package/plugins/pbr/skills/discuss/templates/decision-categories.md +9 -0
- package/plugins/pbr/skills/explore/SKILL.md +362 -0
- package/plugins/pbr/skills/health/SKILL.md +186 -0
- package/plugins/pbr/skills/health/templates/check-pattern.md.tmpl +30 -0
- package/plugins/pbr/skills/health/templates/output-format.md.tmpl +63 -0
- package/plugins/pbr/skills/help/SKILL.md +140 -0
- package/plugins/pbr/skills/import/SKILL.md +490 -0
- package/plugins/pbr/skills/milestone/SKILL.md +673 -0
- package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +48 -0
- package/plugins/pbr/skills/milestone/templates/stats-file.md.tmpl +30 -0
- package/plugins/pbr/skills/note/SKILL.md +212 -0
- package/plugins/pbr/skills/pause/SKILL.md +235 -0
- package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +71 -0
- package/plugins/pbr/skills/plan/SKILL.md +628 -0
- package/plugins/pbr/skills/plan/decimal-phase-calc.md +98 -0
- package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +21 -0
- package/plugins/pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -0
- package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +38 -0
- package/plugins/pbr/skills/plan/templates/researcher-prompt.md.tmpl +19 -0
- package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +23 -0
- package/plugins/pbr/skills/quick/SKILL.md +335 -0
- package/plugins/pbr/skills/resume/SKILL.md +388 -0
- package/plugins/pbr/skills/review/SKILL.md +652 -0
- package/plugins/pbr/skills/review/templates/debugger-prompt.md.tmpl +60 -0
- package/plugins/pbr/skills/review/templates/gap-planner-prompt.md.tmpl +40 -0
- package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +115 -0
- package/plugins/pbr/skills/scan/SKILL.md +269 -0
- package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +201 -0
- package/plugins/pbr/skills/setup/SKILL.md +227 -0
- package/plugins/pbr/skills/shared/commit-planning-docs.md +35 -0
- package/plugins/pbr/skills/shared/config-loading.md +102 -0
- package/plugins/pbr/skills/shared/context-budget.md +40 -0
- package/plugins/pbr/skills/shared/context-loader-task.md +86 -0
- package/plugins/pbr/skills/shared/digest-select.md +79 -0
- package/plugins/pbr/skills/shared/domain-probes.md +125 -0
- package/plugins/pbr/skills/shared/error-reporting.md +79 -0
- package/plugins/pbr/skills/shared/gate-prompts.md +388 -0
- package/plugins/pbr/skills/shared/phase-argument-parsing.md +45 -0
- package/plugins/pbr/skills/shared/progress-display.md +53 -0
- package/plugins/pbr/skills/shared/revision-loop.md +81 -0
- package/plugins/pbr/skills/shared/state-loading.md +62 -0
- package/plugins/pbr/skills/shared/state-update.md +161 -0
- package/plugins/pbr/skills/shared/universal-anti-patterns.md +33 -0
- package/plugins/pbr/skills/status/SKILL.md +353 -0
- package/plugins/pbr/skills/todo/SKILL.md +181 -0
- package/plugins/pbr/templates/CONTEXT.md.tmpl +52 -0
- package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +151 -0
- package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +97 -0
- package/plugins/pbr/templates/ROADMAP.md.tmpl +40 -0
- package/plugins/pbr/templates/SUMMARY.md.tmpl +81 -0
- package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +116 -0
- package/plugins/pbr/templates/codebase/ARCHITECTURE.md.tmpl +98 -0
- package/plugins/pbr/templates/codebase/CONCERNS.md.tmpl +93 -0
- package/plugins/pbr/templates/codebase/CONVENTIONS.md.tmpl +104 -0
- package/plugins/pbr/templates/codebase/INTEGRATIONS.md.tmpl +78 -0
- package/plugins/pbr/templates/codebase/STACK.md.tmpl +78 -0
- package/plugins/pbr/templates/codebase/STRUCTURE.md.tmpl +80 -0
- package/plugins/pbr/templates/codebase/TESTING.md.tmpl +107 -0
- package/plugins/pbr/templates/continue-here.md.tmpl +73 -0
- package/plugins/pbr/templates/prompt-partials/phase-project-context.md.tmpl +37 -0
- package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +124 -0
- package/plugins/pbr/templates/research/STACK.md.tmpl +71 -0
- package/plugins/pbr/templates/research/SUMMARY.md.tmpl +112 -0
- package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +81 -0
- package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +99 -0
- package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +36 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PreToolUse hook (Write|Edit): Enforces skill-specific workflow rules.
|
|
5
|
+
*
|
|
6
|
+
* Reads .planning/.active-skill to determine which skill is running.
|
|
7
|
+
* Each skill can have rules about what files can be written and when.
|
|
8
|
+
*
|
|
9
|
+
* Current rules:
|
|
10
|
+
* - /pbr:quick: Cannot write files outside .planning/ until a PLAN.md
|
|
11
|
+
* exists in .planning/quick/. This prevents the orchestrator from
|
|
12
|
+
* skipping the planning steps and jumping straight to implementation.
|
|
13
|
+
*
|
|
14
|
+
* Skills opt in by writing .planning/.active-skill at the start of
|
|
15
|
+
* their execution. If the file doesn't exist, this hook does nothing.
|
|
16
|
+
*
|
|
17
|
+
* Exit codes:
|
|
18
|
+
* 0 = allowed or not applicable
|
|
19
|
+
* 2 = blocked (workflow violation)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const { logHook } = require('./hook-logger');
|
|
25
|
+
const { logEvent } = require('./event-logger');
|
|
26
|
+
|
|
27
|
+
function main() {
|
|
28
|
+
let input = '';
|
|
29
|
+
|
|
30
|
+
process.stdin.setEncoding('utf8');
|
|
31
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
32
|
+
process.stdin.on('end', () => {
|
|
33
|
+
try {
|
|
34
|
+
const data = JSON.parse(input);
|
|
35
|
+
const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
|
|
36
|
+
|
|
37
|
+
if (!filePath) {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const cwd = process.cwd();
|
|
42
|
+
const planningDir = path.join(cwd, '.planning');
|
|
43
|
+
|
|
44
|
+
// Read active skill
|
|
45
|
+
const activeSkill = readActiveSkill(planningDir);
|
|
46
|
+
if (!activeSkill) {
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Apply skill-specific rules
|
|
51
|
+
const violation = checkSkillRules(activeSkill, filePath, planningDir);
|
|
52
|
+
if (violation) {
|
|
53
|
+
logHook('check-skill-workflow', 'PreToolUse', 'block', {
|
|
54
|
+
skill: activeSkill,
|
|
55
|
+
file: path.basename(filePath),
|
|
56
|
+
rule: violation.rule
|
|
57
|
+
});
|
|
58
|
+
logEvent('workflow', 'skill-workflow-block', {
|
|
59
|
+
skill: activeSkill,
|
|
60
|
+
file: path.basename(filePath),
|
|
61
|
+
rule: violation.rule
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const output = {
|
|
65
|
+
decision: 'block',
|
|
66
|
+
reason: violation.message
|
|
67
|
+
};
|
|
68
|
+
process.stdout.write(JSON.stringify(output));
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
process.exit(0);
|
|
73
|
+
} catch (_e) {
|
|
74
|
+
// Don't block on errors
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readActiveSkill(planningDir) {
|
|
81
|
+
const skillFile = path.join(planningDir, '.active-skill');
|
|
82
|
+
if (!fs.existsSync(skillFile)) return null;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const content = fs.readFileSync(skillFile, 'utf8').trim();
|
|
86
|
+
return content || null;
|
|
87
|
+
} catch (_e) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Check skill-specific workflow rules.
|
|
94
|
+
* Returns { rule, message } if violated, null if OK.
|
|
95
|
+
*/
|
|
96
|
+
function checkSkillRules(skill, filePath, planningDir) {
|
|
97
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
98
|
+
const normalizedPlanning = planningDir.replace(/\\/g, '/');
|
|
99
|
+
// Check with both raw paths and resolved symlinks (macOS /var → /private/var)
|
|
100
|
+
let isInPlanning = normalizedPath.startsWith(normalizedPlanning);
|
|
101
|
+
if (!isInPlanning) {
|
|
102
|
+
try {
|
|
103
|
+
const resolvedPlanning = fs.realpathSync(planningDir).replace(/\\/g, '/');
|
|
104
|
+
isInPlanning = normalizedPath.startsWith(resolvedPlanning);
|
|
105
|
+
} catch (_e) { /* not resolvable */ }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check for orchestrator writing agent artifacts (any skill)
|
|
109
|
+
const artifactViolation = checkArtifactRules(filePath, planningDir);
|
|
110
|
+
if (artifactViolation) return artifactViolation;
|
|
111
|
+
|
|
112
|
+
switch (skill) {
|
|
113
|
+
case 'quick':
|
|
114
|
+
return checkQuickRules(filePath, isInPlanning, planningDir);
|
|
115
|
+
case 'build':
|
|
116
|
+
return checkBuildRules(filePath, isInPlanning, planningDir);
|
|
117
|
+
default:
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Artifact rules (all skills):
|
|
124
|
+
* - SUMMARY.md and VERIFICATION.md should only be written by subagents
|
|
125
|
+
* - If .active-agent exists, a subagent is running (allow writes)
|
|
126
|
+
* - If .active-agent does NOT exist, the orchestrator is writing (block)
|
|
127
|
+
*/
|
|
128
|
+
function checkArtifactRules(filePath, planningDir) {
|
|
129
|
+
const basename = path.basename(filePath);
|
|
130
|
+
|
|
131
|
+
// Only check SUMMARY and VERIFICATION files in phase directories
|
|
132
|
+
const isSummary = /^SUMMARY.*\.md$/i.test(basename);
|
|
133
|
+
const isVerification = /^VERIFICATION.*\.md$/i.test(basename);
|
|
134
|
+
if (!isSummary && !isVerification) return null;
|
|
135
|
+
|
|
136
|
+
// If .active-agent exists, a subagent is running — allow
|
|
137
|
+
const activeAgentFile = path.join(planningDir, '.active-agent');
|
|
138
|
+
if (fs.existsSync(activeAgentFile)) return null;
|
|
139
|
+
|
|
140
|
+
const artifactType = isSummary ? 'SUMMARY.md' : 'VERIFICATION.md';
|
|
141
|
+
return {
|
|
142
|
+
rule: 'orchestrator-artifact-write',
|
|
143
|
+
message: `Workflow violation: ${artifactType} should be written by a subagent, not the orchestrator.\n\nBlocked: ${filePath}\n\nDelegate this write to a Task(subagent_type: "pbr:executor") or Task(subagent_type: "pbr:verifier") agent.`
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* /pbr:quick rules:
|
|
149
|
+
* - Cannot write files outside .planning/ until PLAN.md exists in .planning/quick/
|
|
150
|
+
* - This prevents the orchestrator from skipping directly to implementation
|
|
151
|
+
*/
|
|
152
|
+
function checkQuickRules(filePath, isInPlanning, planningDir) {
|
|
153
|
+
// Writes to .planning/ are always allowed (creating plan, state, etc.)
|
|
154
|
+
if (isInPlanning) return null;
|
|
155
|
+
|
|
156
|
+
// Check if any PLAN.md exists under .planning/quick/
|
|
157
|
+
const quickDir = path.join(planningDir, 'quick');
|
|
158
|
+
if (hasPlanFile(quickDir)) return null;
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
rule: 'quick-requires-plan',
|
|
162
|
+
message: `Workflow violation: /pbr:quick must create a PLAN.md before writing source code.\n\nBlocked: ${filePath}\n\nComplete Steps 4-6 of the quick workflow first:\n 1. Create .planning/quick/{NNN}-{slug}/ directory\n 2. Write PLAN.md with at least one <task> block\n 3. Then spawn the executor to implement`
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* /pbr:build rules:
|
|
168
|
+
* - Cannot write files outside .planning/ unless a PLAN.md exists for the current phase
|
|
169
|
+
*/
|
|
170
|
+
function checkBuildRules(filePath, isInPlanning, planningDir) {
|
|
171
|
+
// Writes to .planning/ are always allowed
|
|
172
|
+
if (isInPlanning) return null;
|
|
173
|
+
|
|
174
|
+
// Check if any PLAN.md exists under .planning/phases/
|
|
175
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
176
|
+
if (!fs.existsSync(phasesDir)) {
|
|
177
|
+
return {
|
|
178
|
+
rule: 'build-requires-plan',
|
|
179
|
+
message: `Workflow violation: /pbr:build requires a planned phase before writing source code.\n\nBlocked: ${filePath}\n\nRun /pbr:plan first to create a phase plan.`
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check current phase directory for PLAN.md
|
|
184
|
+
const stateFile = path.join(planningDir, 'STATE.md');
|
|
185
|
+
if (!fs.existsSync(stateFile)) return null;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const state = fs.readFileSync(stateFile, 'utf8');
|
|
189
|
+
const phaseMatch = state.match(/Phase:\s*(\d+)\s+of\s+\d+/);
|
|
190
|
+
if (!phaseMatch) return null;
|
|
191
|
+
|
|
192
|
+
const currentPhase = phaseMatch[1].padStart(2, '0');
|
|
193
|
+
const dirs = fs.readdirSync(phasesDir).filter(d => d.startsWith(currentPhase));
|
|
194
|
+
if (dirs.length === 0) return null;
|
|
195
|
+
|
|
196
|
+
const phaseDir = path.join(phasesDir, dirs[0]);
|
|
197
|
+
if (hasPlanFile(phaseDir)) return null;
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
rule: 'build-requires-plan',
|
|
201
|
+
message: `Workflow violation: /pbr:build requires a PLAN.md for phase ${currentPhase} before writing source code.\n\nBlocked: ${filePath}\n\nRun /pbr:plan ${currentPhase} first.`
|
|
202
|
+
};
|
|
203
|
+
} catch (_e) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check if any PLAN.md file exists in a directory (recursive one level).
|
|
210
|
+
*/
|
|
211
|
+
function hasPlanFile(dir) {
|
|
212
|
+
if (!fs.existsSync(dir)) return false;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
216
|
+
for (const entry of entries) {
|
|
217
|
+
if (entry.isFile() && entry.name.endsWith('PLAN.md')) return true;
|
|
218
|
+
if (entry.isDirectory()) {
|
|
219
|
+
const subEntries = fs.readdirSync(path.join(dir, entry.name));
|
|
220
|
+
if (subEntries.some(f => f.endsWith('PLAN.md'))) return true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} catch (_e) {
|
|
224
|
+
// skip
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Core workflow check logic for use by dispatchers.
|
|
231
|
+
* @param {Object} data - Parsed hook input (tool_input, etc.)
|
|
232
|
+
* @returns {null|{exitCode: number, output: Object}} null if pass, result otherwise
|
|
233
|
+
*/
|
|
234
|
+
function checkWorkflow(data) {
|
|
235
|
+
const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
|
|
236
|
+
if (!filePath) return null;
|
|
237
|
+
|
|
238
|
+
const cwd = process.cwd();
|
|
239
|
+
const planningDir = path.join(cwd, '.planning');
|
|
240
|
+
|
|
241
|
+
const activeSkill = readActiveSkill(planningDir);
|
|
242
|
+
if (!activeSkill) return null;
|
|
243
|
+
|
|
244
|
+
const violation = checkSkillRules(activeSkill, filePath, planningDir);
|
|
245
|
+
if (violation) {
|
|
246
|
+
logHook('check-skill-workflow', 'PreToolUse', 'block', {
|
|
247
|
+
skill: activeSkill, file: path.basename(filePath), rule: violation.rule
|
|
248
|
+
});
|
|
249
|
+
logEvent('workflow', 'skill-workflow-block', {
|
|
250
|
+
skill: activeSkill, file: path.basename(filePath), rule: violation.rule
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
exitCode: 2,
|
|
254
|
+
output: { decision: 'block', reason: violation.message }
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = { readActiveSkill, checkSkillRules, hasPlanFile, checkWorkflow };
|
|
262
|
+
if (require.main === module) { main(); }
|