@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,200 +1,200 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* PreToolUse hook: Validates git commit message format.
|
|
5
|
-
*
|
|
6
|
-
* Expected format: {type}({phase}-{plan}): {description}
|
|
7
|
-
* Valid types: feat, fix, refactor, test, docs, chore
|
|
8
|
-
*
|
|
9
|
-
* Also accepts:
|
|
10
|
-
* - Merge commits (starts with "Merge")
|
|
11
|
-
* - Quick task commits: {type}(quick-{NNN}): {description}
|
|
12
|
-
* - Planning doc commits: docs(planning): {description}
|
|
13
|
-
* - WIP commits: wip: {description} or wip({area}): {description}
|
|
14
|
-
*
|
|
15
|
-
* Exit codes:
|
|
16
|
-
* 0 = not a commit command or valid format
|
|
17
|
-
* 2 = invalid commit message format (blocks the tool)
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
const path = require('path');
|
|
21
|
-
const { execSync } = require('child_process');
|
|
22
|
-
const { logHook } = require('./hook-logger');
|
|
23
|
-
const { logEvent } = require('./event-logger');
|
|
24
|
-
|
|
25
|
-
const VALID_TYPES = ['feat', 'fix', 'refactor', 'test', 'docs', 'chore', 'wip'];
|
|
26
|
-
|
|
27
|
-
const SENSITIVE_PATTERNS = [
|
|
28
|
-
/^\.env$/, // .env exactly (not .env.example)
|
|
29
|
-
/\.env\.[^.]+$/, // .env.production, .env.local etc (but not .env.example)
|
|
30
|
-
/\.key$/i,
|
|
31
|
-
/\.pem$/i,
|
|
32
|
-
/\.pfx$/i,
|
|
33
|
-
/\.p12$/i,
|
|
34
|
-
/credential/i,
|
|
35
|
-
/secret/i,
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const SAFE_PATTERNS = [
|
|
39
|
-
/\.example$/i,
|
|
40
|
-
/\.template$/i,
|
|
41
|
-
/\.sample$/i,
|
|
42
|
-
/^tests?[\\/]/i,
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
// Pattern: type(scope): description
|
|
46
|
-
// Scope can be: NN-MM (phase-plan), quick-NNN, planning, or any word
|
|
47
|
-
const COMMIT_PATTERN = /^(feat|fix|refactor|test|docs|chore|wip)(\([a-zA-Z0-9._-]+\))?:\s+.+/;
|
|
48
|
-
|
|
49
|
-
// Merge commits are always allowed
|
|
50
|
-
const MERGE_PATTERN = /^Merge\s/;
|
|
51
|
-
|
|
52
|
-
// AI co-author patterns to block
|
|
53
|
-
const AI_COAUTHOR_PATTERN = /Co-Authored-By:.*(?:Claude|Anthropic|noreply@anthropic\.com|OpenAI|Copilot|GPT|AI Assistant)/i;
|
|
54
|
-
|
|
55
|
-
function checkAiCoAuthorResult(command) {
|
|
56
|
-
if (AI_COAUTHOR_PATTERN.test(command)) {
|
|
57
|
-
logHook('validate-commit', 'PreToolUse', 'block-coauthor', { command: command.substring(0, 200) });
|
|
58
|
-
return {
|
|
59
|
-
output: {
|
|
60
|
-
decision: 'block',
|
|
61
|
-
reason: 'Commit blocked: contains AI co-author attribution.\n\nPlan-Build-Run commits must not include Co-Authored-By lines referencing AI tools (Claude, Copilot, GPT, etc.).\n\nRemove the Co-Authored-By line and try again.'
|
|
62
|
-
},
|
|
63
|
-
exitCode: 2
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function checkSensitiveFilesResult() {
|
|
70
|
-
try {
|
|
71
|
-
const output = execSync('git diff --cached --name-only', { encoding: 'utf8' });
|
|
72
|
-
const files = output.trim().split('\n').filter(Boolean);
|
|
73
|
-
|
|
74
|
-
const matched = files.filter((file) => {
|
|
75
|
-
// Skip files matching safe patterns
|
|
76
|
-
if (SAFE_PATTERNS.some((pattern) => pattern.test(file))) return false;
|
|
77
|
-
// Check against sensitive patterns (test basename and full path)
|
|
78
|
-
const basename = path.basename(file);
|
|
79
|
-
return SENSITIVE_PATTERNS.some((pattern) => pattern.test(basename) || pattern.test(file));
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
if (matched.length > 0) {
|
|
83
|
-
logHook('validate-commit', 'PreToolUse', 'block-sensitive', { files: matched });
|
|
84
|
-
return {
|
|
85
|
-
output: {
|
|
86
|
-
decision: 'block',
|
|
87
|
-
reason: `Commit blocked: staged files may contain sensitive data.\n\nFiles: ${matched.join(', ')}\n\nRemove these files from staging with:\n git reset HEAD ${matched.join(' ')}\n\nIf these files are intentionally safe (e.g., test fixtures), rename them to include .example, .template, or .sample.`
|
|
88
|
-
},
|
|
89
|
-
exitCode: 2
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
} catch (_e) {
|
|
93
|
-
// Not in a git repo or git not available - silently continue
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Check a parsed hook data object for commit validation issues.
|
|
100
|
-
* Returns { output, exitCode } if the command should be blocked, or null if allowed.
|
|
101
|
-
* Used by pre-bash-dispatch.js for consolidated hook execution.
|
|
102
|
-
*/
|
|
103
|
-
function checkCommit(data) {
|
|
104
|
-
const command = data.tool_input?.command || '';
|
|
105
|
-
|
|
106
|
-
// Only validate git commit commands
|
|
107
|
-
if (!isGitCommit(command)) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Extract the commit message
|
|
112
|
-
const message = extractCommitMessage(command);
|
|
113
|
-
if (!message) {
|
|
114
|
-
// Could not parse message - let it through (might be --amend or other form)
|
|
115
|
-
logHook('validate-commit', 'PreToolUse', 'allow', { reason: 'unparseable message' });
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Validate format
|
|
120
|
-
if (MERGE_PATTERN.test(message)) {
|
|
121
|
-
logHook('validate-commit', 'PreToolUse', 'allow', { message, reason: 'merge commit' });
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!COMMIT_PATTERN.test(message)) {
|
|
126
|
-
logHook('validate-commit', 'PreToolUse', 'block', { message });
|
|
127
|
-
logEvent('workflow', 'commit-validated', { message: message.substring(0, 80), status: 'block' });
|
|
128
|
-
return {
|
|
129
|
-
output: {
|
|
130
|
-
decision: 'block',
|
|
131
|
-
reason: `Invalid commit message format.\n\nExpected: {type}({scope}): {description}\nTypes: ${VALID_TYPES.join(', ')}\nExamples:\n feat(03-01): add user authentication\n fix(02-02): resolve database connection timeout\n docs(planning): update roadmap with phase 4\n wip: save progress on auth middleware\n\nGot: "${message}"`
|
|
132
|
-
},
|
|
133
|
-
exitCode: 2
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Valid format
|
|
138
|
-
logHook('validate-commit', 'PreToolUse', 'allow', { message });
|
|
139
|
-
logEvent('workflow', 'commit-validated', { message: message.substring(0, 80), status: 'allow' });
|
|
140
|
-
|
|
141
|
-
// Check AI co-author
|
|
142
|
-
const coAuthorResult = checkAiCoAuthorResult(command);
|
|
143
|
-
if (coAuthorResult) return coAuthorResult;
|
|
144
|
-
|
|
145
|
-
// Check sensitive files
|
|
146
|
-
const sensitiveResult = checkSensitiveFilesResult();
|
|
147
|
-
if (sensitiveResult) return sensitiveResult;
|
|
148
|
-
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function main() {
|
|
153
|
-
let input = '';
|
|
154
|
-
|
|
155
|
-
process.stdin.setEncoding('utf8');
|
|
156
|
-
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
157
|
-
process.stdin.on('end', () => {
|
|
158
|
-
try {
|
|
159
|
-
const data = JSON.parse(input);
|
|
160
|
-
const result = checkCommit(data);
|
|
161
|
-
if (result) {
|
|
162
|
-
process.stdout.write(JSON.stringify(result.output));
|
|
163
|
-
process.exit(result.exitCode);
|
|
164
|
-
}
|
|
165
|
-
process.exit(0);
|
|
166
|
-
} catch (_e) {
|
|
167
|
-
// Parse error - don't block
|
|
168
|
-
process.exit(0);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function isGitCommit(command) {
|
|
174
|
-
// Match: git commit anywhere in the command string
|
|
175
|
-
// Handles chained commands like "cd /dir && git commit ..." or "git add . && git commit ..."
|
|
176
|
-
const trimmed = command.trim();
|
|
177
|
-
return /\bgit\s+commit\b/.test(trimmed) && !trimmed.includes('--amend --no-edit');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function extractCommitMessage(command) {
|
|
181
|
-
// Try -m "message" or -m 'message'
|
|
182
|
-
const mFlagMatch = command.match(/-m\s+["']([^"']+)["']/);
|
|
183
|
-
if (mFlagMatch) return mFlagMatch[1];
|
|
184
|
-
|
|
185
|
-
// Try -m "message" with escaped quotes
|
|
186
|
-
const mFlagMatch2 = command.match(/-m\s+"([^"]+)"/);
|
|
187
|
-
if (mFlagMatch2) return mFlagMatch2[1];
|
|
188
|
-
|
|
189
|
-
// Try heredoc: -m "$(cat <<'EOF'\n...\nEOF\n)"
|
|
190
|
-
const heredocMatch = command.match(/<<'?EOF'?\s*\n([\s\S]*?)\nEOF/);
|
|
191
|
-
if (heredocMatch) {
|
|
192
|
-
// First line of heredoc is the commit message
|
|
193
|
-
return heredocMatch[1].trim().split('\n')[0].trim();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
module.exports = { checkCommit };
|
|
200
|
-
if (require.main === module) { main(); }
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PreToolUse hook: Validates git commit message format.
|
|
5
|
+
*
|
|
6
|
+
* Expected format: {type}({phase}-{plan}): {description}
|
|
7
|
+
* Valid types: feat, fix, refactor, test, docs, chore
|
|
8
|
+
*
|
|
9
|
+
* Also accepts:
|
|
10
|
+
* - Merge commits (starts with "Merge")
|
|
11
|
+
* - Quick task commits: {type}(quick-{NNN}): {description}
|
|
12
|
+
* - Planning doc commits: docs(planning): {description}
|
|
13
|
+
* - WIP commits: wip: {description} or wip({area}): {description}
|
|
14
|
+
*
|
|
15
|
+
* Exit codes:
|
|
16
|
+
* 0 = not a commit command or valid format
|
|
17
|
+
* 2 = invalid commit message format (blocks the tool)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const { execSync } = require('child_process');
|
|
22
|
+
const { logHook } = require('./hook-logger');
|
|
23
|
+
const { logEvent } = require('./event-logger');
|
|
24
|
+
|
|
25
|
+
const VALID_TYPES = ['feat', 'fix', 'refactor', 'test', 'docs', 'chore', 'wip'];
|
|
26
|
+
|
|
27
|
+
const SENSITIVE_PATTERNS = [
|
|
28
|
+
/^\.env$/, // .env exactly (not .env.example)
|
|
29
|
+
/\.env\.[^.]+$/, // .env.production, .env.local etc (but not .env.example)
|
|
30
|
+
/\.key$/i,
|
|
31
|
+
/\.pem$/i,
|
|
32
|
+
/\.pfx$/i,
|
|
33
|
+
/\.p12$/i,
|
|
34
|
+
/credential/i,
|
|
35
|
+
/secret/i,
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const SAFE_PATTERNS = [
|
|
39
|
+
/\.example$/i,
|
|
40
|
+
/\.template$/i,
|
|
41
|
+
/\.sample$/i,
|
|
42
|
+
/^tests?[\\/]/i,
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// Pattern: type(scope): description
|
|
46
|
+
// Scope can be: NN-MM (phase-plan), quick-NNN, planning, or any word
|
|
47
|
+
const COMMIT_PATTERN = /^(feat|fix|refactor|test|docs|chore|wip)(\([a-zA-Z0-9._-]+\))?:\s+.+/;
|
|
48
|
+
|
|
49
|
+
// Merge commits are always allowed
|
|
50
|
+
const MERGE_PATTERN = /^Merge\s/;
|
|
51
|
+
|
|
52
|
+
// AI co-author patterns to block
|
|
53
|
+
const AI_COAUTHOR_PATTERN = /Co-Authored-By:.*(?:Claude|Anthropic|noreply@anthropic\.com|OpenAI|Copilot|GPT|AI Assistant)/i;
|
|
54
|
+
|
|
55
|
+
function checkAiCoAuthorResult(command) {
|
|
56
|
+
if (AI_COAUTHOR_PATTERN.test(command)) {
|
|
57
|
+
logHook('validate-commit', 'PreToolUse', 'block-coauthor', { command: command.substring(0, 200) });
|
|
58
|
+
return {
|
|
59
|
+
output: {
|
|
60
|
+
decision: 'block',
|
|
61
|
+
reason: 'Commit blocked: contains AI co-author attribution.\n\nPlan-Build-Run commits must not include Co-Authored-By lines referencing AI tools (Claude, Copilot, GPT, etc.).\n\nRemove the Co-Authored-By line and try again.'
|
|
62
|
+
},
|
|
63
|
+
exitCode: 2
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function checkSensitiveFilesResult() {
|
|
70
|
+
try {
|
|
71
|
+
const output = execSync('git diff --cached --name-only', { encoding: 'utf8' });
|
|
72
|
+
const files = output.trim().split('\n').filter(Boolean);
|
|
73
|
+
|
|
74
|
+
const matched = files.filter((file) => {
|
|
75
|
+
// Skip files matching safe patterns
|
|
76
|
+
if (SAFE_PATTERNS.some((pattern) => pattern.test(file))) return false;
|
|
77
|
+
// Check against sensitive patterns (test basename and full path)
|
|
78
|
+
const basename = path.basename(file);
|
|
79
|
+
return SENSITIVE_PATTERNS.some((pattern) => pattern.test(basename) || pattern.test(file));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (matched.length > 0) {
|
|
83
|
+
logHook('validate-commit', 'PreToolUse', 'block-sensitive', { files: matched });
|
|
84
|
+
return {
|
|
85
|
+
output: {
|
|
86
|
+
decision: 'block',
|
|
87
|
+
reason: `Commit blocked: staged files may contain sensitive data.\n\nFiles: ${matched.join(', ')}\n\nRemove these files from staging with:\n git reset HEAD ${matched.join(' ')}\n\nIf these files are intentionally safe (e.g., test fixtures), rename them to include .example, .template, or .sample.`
|
|
88
|
+
},
|
|
89
|
+
exitCode: 2
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
} catch (_e) {
|
|
93
|
+
// Not in a git repo or git not available - silently continue
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check a parsed hook data object for commit validation issues.
|
|
100
|
+
* Returns { output, exitCode } if the command should be blocked, or null if allowed.
|
|
101
|
+
* Used by pre-bash-dispatch.js for consolidated hook execution.
|
|
102
|
+
*/
|
|
103
|
+
function checkCommit(data) {
|
|
104
|
+
const command = data.tool_input?.command || '';
|
|
105
|
+
|
|
106
|
+
// Only validate git commit commands
|
|
107
|
+
if (!isGitCommit(command)) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Extract the commit message
|
|
112
|
+
const message = extractCommitMessage(command);
|
|
113
|
+
if (!message) {
|
|
114
|
+
// Could not parse message - let it through (might be --amend or other form)
|
|
115
|
+
logHook('validate-commit', 'PreToolUse', 'allow', { reason: 'unparseable message' });
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Validate format
|
|
120
|
+
if (MERGE_PATTERN.test(message)) {
|
|
121
|
+
logHook('validate-commit', 'PreToolUse', 'allow', { message, reason: 'merge commit' });
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!COMMIT_PATTERN.test(message)) {
|
|
126
|
+
logHook('validate-commit', 'PreToolUse', 'block', { message });
|
|
127
|
+
logEvent('workflow', 'commit-validated', { message: message.substring(0, 80), status: 'block' });
|
|
128
|
+
return {
|
|
129
|
+
output: {
|
|
130
|
+
decision: 'block',
|
|
131
|
+
reason: `Invalid commit message format.\n\nExpected: {type}({scope}): {description}\nTypes: ${VALID_TYPES.join(', ')}\nExamples:\n feat(03-01): add user authentication\n fix(02-02): resolve database connection timeout\n docs(planning): update roadmap with phase 4\n wip: save progress on auth middleware\n\nGot: "${message}"`
|
|
132
|
+
},
|
|
133
|
+
exitCode: 2
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Valid format
|
|
138
|
+
logHook('validate-commit', 'PreToolUse', 'allow', { message });
|
|
139
|
+
logEvent('workflow', 'commit-validated', { message: message.substring(0, 80), status: 'allow' });
|
|
140
|
+
|
|
141
|
+
// Check AI co-author
|
|
142
|
+
const coAuthorResult = checkAiCoAuthorResult(command);
|
|
143
|
+
if (coAuthorResult) return coAuthorResult;
|
|
144
|
+
|
|
145
|
+
// Check sensitive files
|
|
146
|
+
const sensitiveResult = checkSensitiveFilesResult();
|
|
147
|
+
if (sensitiveResult) return sensitiveResult;
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function main() {
|
|
153
|
+
let input = '';
|
|
154
|
+
|
|
155
|
+
process.stdin.setEncoding('utf8');
|
|
156
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
157
|
+
process.stdin.on('end', () => {
|
|
158
|
+
try {
|
|
159
|
+
const data = JSON.parse(input);
|
|
160
|
+
const result = checkCommit(data);
|
|
161
|
+
if (result) {
|
|
162
|
+
process.stdout.write(JSON.stringify(result.output));
|
|
163
|
+
process.exit(result.exitCode);
|
|
164
|
+
}
|
|
165
|
+
process.exit(0);
|
|
166
|
+
} catch (_e) {
|
|
167
|
+
// Parse error - don't block
|
|
168
|
+
process.exit(0);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isGitCommit(command) {
|
|
174
|
+
// Match: git commit anywhere in the command string
|
|
175
|
+
// Handles chained commands like "cd /dir && git commit ..." or "git add . && git commit ..."
|
|
176
|
+
const trimmed = command.trim();
|
|
177
|
+
return /\bgit\s+commit\b/.test(trimmed) && !trimmed.includes('--amend --no-edit');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function extractCommitMessage(command) {
|
|
181
|
+
// Try -m "message" or -m 'message'
|
|
182
|
+
const mFlagMatch = command.match(/-m\s+["']([^"']+)["']/);
|
|
183
|
+
if (mFlagMatch) return mFlagMatch[1];
|
|
184
|
+
|
|
185
|
+
// Try -m "message" with escaped quotes
|
|
186
|
+
const mFlagMatch2 = command.match(/-m\s+"([^"]+)"/);
|
|
187
|
+
if (mFlagMatch2) return mFlagMatch2[1];
|
|
188
|
+
|
|
189
|
+
// Try heredoc: -m "$(cat <<'EOF'\n...\nEOF\n)"
|
|
190
|
+
const heredocMatch = command.match(/<<'?EOF'?\s*\n([\s\S]*?)\nEOF/);
|
|
191
|
+
if (heredocMatch) {
|
|
192
|
+
// First line of heredoc is the commit message
|
|
193
|
+
return heredocMatch[1].trim().split('\n')[0].trim();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = { checkCommit };
|
|
200
|
+
if (require.main === module) { main(); }
|