Skip to content

Commit 2e21d58

Browse files
Codebeastclaude
authored andcommitted
feat(bootstrap): post-bootstrap governance gaps report (#101)
After bootstrap completes, surfaces configured-but-unenforced governance: - requireTrailers enabled but no commit-msg hook → suggests install - drift enabled but no CI workflow → suggests --ci github - no SECURITY.md → suggests adding responsible disclosure doc - no pre-commit hook → suggests ADF evidence gate Gaps appear in text format as "Governance gaps" section and in JSON format as a `governanceGaps` array on the result object. 399 tests green, tsc clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5d9e19e commit 2e21d58

1 file changed

Lines changed: 60 additions & 0 deletions

File tree

packages/cli/src/commands/bootstrap.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,10 +298,70 @@ export async function bootstrapCommand(options: CLIOptions, args: string[]): Pro
298298
reason: 'Commit governance baseline',
299299
});
300300

301+
// ========================================================================
302+
// Governance Gaps — surface what's configured but not enforced
303+
// ========================================================================
304+
const gaps: Array<{ gap: string; fix: string }> = [];
305+
306+
// Check: trailers enabled but no commit-msg hook
307+
if (fs.existsSync('.charter/config.json')) {
308+
try {
309+
const cfg = JSON.parse(fs.readFileSync('.charter/config.json', 'utf-8'));
310+
if (cfg.git?.requireTrailers) {
311+
const hookPath = path.resolve('.githooks/commit-msg');
312+
const gitHookPath = path.resolve('.git/hooks/commit-msg');
313+
if (!fs.existsSync(hookPath) && !fs.existsSync(gitHookPath)) {
314+
gaps.push({
315+
gap: 'requireTrailers enabled but no commit-msg hook installed',
316+
fix: 'charter hook install --commit-msg',
317+
});
318+
}
319+
}
320+
if (cfg.drift?.enabled && !ciTarget) {
321+
const workflowPath = path.resolve('.github/workflows/charter.yml');
322+
if (!fs.existsSync(workflowPath)) {
323+
gaps.push({
324+
gap: 'drift detection enabled but no CI workflow',
325+
fix: 'charter bootstrap --ci github (or add manually)',
326+
});
327+
}
328+
}
329+
} catch { /* config not parseable — doctor already caught it */ }
330+
}
331+
332+
// Check: no SECURITY.md
333+
if (!fs.existsSync('SECURITY.md')) {
334+
gaps.push({
335+
gap: 'no SECURITY.md for responsible disclosure',
336+
fix: 'add SECURITY.md with reporting contact and supported versions',
337+
});
338+
}
339+
340+
// Check: no pre-commit hook for ADF evidence
341+
const preCommitHook = path.resolve('.githooks/pre-commit');
342+
const gitPreCommit = path.resolve('.git/hooks/pre-commit');
343+
if (!fs.existsSync(preCommitHook) && !fs.existsSync(gitPreCommit)) {
344+
gaps.push({
345+
gap: 'no pre-commit hook for ADF evidence gate',
346+
fix: 'charter hook install --pre-commit',
347+
});
348+
}
349+
301350
if (options.format === 'json') {
351+
(result as unknown as Record<string, unknown>).governanceGaps = gaps;
302352
console.log(JSON.stringify(result, null, 2));
303353
} else {
304354
console.log(`Bootstrap complete. ${warnings} warning${warnings === 1 ? '' : 's'}.`);
355+
356+
if (gaps.length > 0) {
357+
console.log('');
358+
console.log('Governance gaps (configured but not enforced):');
359+
for (const { gap, fix } of gaps) {
360+
console.log(` ⚠ ${gap}`);
361+
console.log(` → ${fix}`);
362+
}
363+
}
364+
305365
console.log('');
306366
console.log('Next steps:');
307367
result.nextSteps.forEach((step, i) => {

0 commit comments

Comments
 (0)