delimit-cli 4.1.30 → 4.1.32
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/README.md +2 -0
- package/bin/delimit-cli.js +134 -0
- package/lib/cross-model-hooks.js +49 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -156,6 +156,8 @@ npx delimit-cli diff old.yaml new.yaml # Compare two specs
|
|
|
156
156
|
npx delimit-cli explain old.yaml new.yaml # Generate migration guide
|
|
157
157
|
npx delimit-cli check # Pre-commit governance check
|
|
158
158
|
npx delimit-cli check --staged --fix # Check staged files + show guidance
|
|
159
|
+
npx delimit-cli hooks install # Install git pre-commit hook
|
|
160
|
+
npx delimit-cli hooks install --pre-push # Also add pre-push hook
|
|
159
161
|
npx delimit-cli ci # Generate GitHub Action workflow
|
|
160
162
|
npx delimit-cli ci --strict --dry-run # Preview strict workflow
|
|
161
163
|
npx delimit-cli doctor # Check setup health
|
package/bin/delimit-cli.js
CHANGED
|
@@ -3044,6 +3044,140 @@ program
|
|
|
3044
3044
|
}
|
|
3045
3045
|
});
|
|
3046
3046
|
|
|
3047
|
+
// Hooks command — install/remove git hooks for governance
|
|
3048
|
+
program
|
|
3049
|
+
.command('hooks <action>')
|
|
3050
|
+
.description('Install or remove git hooks (install | remove | status)')
|
|
3051
|
+
.option('--pre-push', 'Also add pre-push hook')
|
|
3052
|
+
.action(async (action, opts) => {
|
|
3053
|
+
const projectDir = process.cwd();
|
|
3054
|
+
const gitDir = path.join(projectDir, '.git');
|
|
3055
|
+
|
|
3056
|
+
if (!fs.existsSync(gitDir)) {
|
|
3057
|
+
console.log(chalk.red('\n Not a git repository. Run git init first.\n'));
|
|
3058
|
+
process.exitCode = 1;
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
const hooksDir = path.join(gitDir, 'hooks');
|
|
3063
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
3064
|
+
|
|
3065
|
+
const preCommitPath = path.join(hooksDir, 'pre-commit');
|
|
3066
|
+
const prePushPath = path.join(hooksDir, 'pre-push');
|
|
3067
|
+
const marker = '# delimit-governance-hook';
|
|
3068
|
+
|
|
3069
|
+
const preCommitHook = `#!/bin/sh
|
|
3070
|
+
${marker}
|
|
3071
|
+
# Delimit API governance gate
|
|
3072
|
+
# Blocks commits with breaking API changes
|
|
3073
|
+
npx delimit-cli check --staged
|
|
3074
|
+
`;
|
|
3075
|
+
|
|
3076
|
+
const prePushHook = `#!/bin/sh
|
|
3077
|
+
${marker}
|
|
3078
|
+
# Delimit API governance gate
|
|
3079
|
+
# Blocks pushes with breaking API changes
|
|
3080
|
+
npx delimit-cli check --base origin/main
|
|
3081
|
+
`;
|
|
3082
|
+
|
|
3083
|
+
if (action === 'install') {
|
|
3084
|
+
console.log(chalk.bold('\n Delimit Hooks\n'));
|
|
3085
|
+
|
|
3086
|
+
let installed = 0;
|
|
3087
|
+
|
|
3088
|
+
// Pre-commit hook
|
|
3089
|
+
if (fs.existsSync(preCommitPath)) {
|
|
3090
|
+
const existing = fs.readFileSync(preCommitPath, 'utf-8');
|
|
3091
|
+
if (existing.includes(marker)) {
|
|
3092
|
+
console.log(chalk.gray(' pre-commit hook already installed'));
|
|
3093
|
+
} else {
|
|
3094
|
+
// Append to existing hook
|
|
3095
|
+
fs.appendFileSync(preCommitPath, '\n' + preCommitHook.split('\n').slice(1).join('\n'));
|
|
3096
|
+
console.log(chalk.green(' + pre-commit hook added (appended to existing)'));
|
|
3097
|
+
installed++;
|
|
3098
|
+
}
|
|
3099
|
+
} else {
|
|
3100
|
+
fs.writeFileSync(preCommitPath, preCommitHook);
|
|
3101
|
+
fs.chmodSync(preCommitPath, '755');
|
|
3102
|
+
console.log(chalk.green(' + pre-commit hook installed'));
|
|
3103
|
+
installed++;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
// Pre-push hook (optional)
|
|
3107
|
+
if (opts.prePush) {
|
|
3108
|
+
if (fs.existsSync(prePushPath)) {
|
|
3109
|
+
const existing = fs.readFileSync(prePushPath, 'utf-8');
|
|
3110
|
+
if (existing.includes(marker)) {
|
|
3111
|
+
console.log(chalk.gray(' pre-push hook already installed'));
|
|
3112
|
+
} else {
|
|
3113
|
+
fs.appendFileSync(prePushPath, '\n' + prePushHook.split('\n').slice(1).join('\n'));
|
|
3114
|
+
console.log(chalk.green(' + pre-push hook added (appended to existing)'));
|
|
3115
|
+
installed++;
|
|
3116
|
+
}
|
|
3117
|
+
} else {
|
|
3118
|
+
fs.writeFileSync(prePushPath, prePushHook);
|
|
3119
|
+
fs.chmodSync(prePushPath, '755');
|
|
3120
|
+
console.log(chalk.green(' + pre-push hook installed'));
|
|
3121
|
+
installed++;
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
if (installed > 0) {
|
|
3126
|
+
console.log(chalk.bold(`\n ${installed} hook(s) installed.`));
|
|
3127
|
+
console.log(chalk.gray(' Commits that introduce breaking API changes will be blocked.'));
|
|
3128
|
+
console.log(chalk.gray(' Override with: git commit --no-verify\n'));
|
|
3129
|
+
} else {
|
|
3130
|
+
console.log(chalk.gray('\n All hooks already installed.\n'));
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
} else if (action === 'remove') {
|
|
3134
|
+
console.log(chalk.bold('\n Delimit Hooks — Remove\n'));
|
|
3135
|
+
let removed = 0;
|
|
3136
|
+
|
|
3137
|
+
for (const [hookPath, name] of [[preCommitPath, 'pre-commit'], [prePushPath, 'pre-push']]) {
|
|
3138
|
+
if (fs.existsSync(hookPath)) {
|
|
3139
|
+
const content = fs.readFileSync(hookPath, 'utf-8');
|
|
3140
|
+
if (content.includes(marker)) {
|
|
3141
|
+
// If the entire hook is ours, remove it
|
|
3142
|
+
const lines = content.split('\n');
|
|
3143
|
+
const delimitStart = lines.findIndex(l => l.includes(marker));
|
|
3144
|
+
if (delimitStart <= 1) {
|
|
3145
|
+
// Whole file is ours
|
|
3146
|
+
fs.unlinkSync(hookPath);
|
|
3147
|
+
console.log(chalk.yellow(` - ${name} hook removed`));
|
|
3148
|
+
} else {
|
|
3149
|
+
// Remove just our section
|
|
3150
|
+
const before = lines.slice(0, delimitStart).join('\n');
|
|
3151
|
+
fs.writeFileSync(hookPath, before + '\n');
|
|
3152
|
+
console.log(chalk.yellow(` - ${name} Delimit section removed`));
|
|
3153
|
+
}
|
|
3154
|
+
removed++;
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
if (removed === 0) {
|
|
3159
|
+
console.log(chalk.gray(' No Delimit hooks found.\n'));
|
|
3160
|
+
} else {
|
|
3161
|
+
console.log(chalk.bold(`\n ${removed} hook(s) removed.\n`));
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
} else if (action === 'status') {
|
|
3165
|
+
console.log(chalk.bold('\n Delimit Hooks — Status\n'));
|
|
3166
|
+
for (const [hookPath, name] of [[preCommitPath, 'pre-commit'], [prePushPath, 'pre-push']]) {
|
|
3167
|
+
if (fs.existsSync(hookPath) && fs.readFileSync(hookPath, 'utf-8').includes(marker)) {
|
|
3168
|
+
console.log(` ${chalk.green('●')} ${name} — installed`);
|
|
3169
|
+
} else {
|
|
3170
|
+
console.log(` ${chalk.gray('○')} ${name} — not installed`);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
console.log('');
|
|
3174
|
+
|
|
3175
|
+
} else {
|
|
3176
|
+
console.log(chalk.red(`\n Unknown action: ${action}`));
|
|
3177
|
+
console.log(chalk.gray(' Usage: delimit hooks install | remove | status\n'));
|
|
3178
|
+
}
|
|
3179
|
+
});
|
|
3180
|
+
|
|
3047
3181
|
// Check command — pre-commit/pre-push governance check
|
|
3048
3182
|
program
|
|
3049
3183
|
.command('check')
|
package/lib/cross-model-hooks.js
CHANGED
|
@@ -530,6 +530,54 @@ fi
|
|
|
530
530
|
}
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
+
// --- Stop hook: session handoff on exit ---
|
|
534
|
+
if (hookConfig.session_start) { // If session-start is enabled, also add session-end
|
|
535
|
+
if (!config.hooks.Stop) {
|
|
536
|
+
config.hooks.Stop = [];
|
|
537
|
+
}
|
|
538
|
+
const home = getHome();
|
|
539
|
+
const hooksDir = path.join(home, '.claude', 'hooks');
|
|
540
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
541
|
+
const stopScript = path.join(hooksDir, 'delimit-stop');
|
|
542
|
+
const delimitHome = path.join(home, '.delimit');
|
|
543
|
+
const stopContent = '#!/bin/bash\n' + `
|
|
544
|
+
# Delimit Stop — session handoff on exit
|
|
545
|
+
# Preserves context for next session across all AI assistants
|
|
546
|
+
DELIMIT_HOME="\${DELIMIT_HOME:-${delimitHome}}"
|
|
547
|
+
LEDGER_DIR="$DELIMIT_HOME/ledger"
|
|
548
|
+
|
|
549
|
+
# Push ledger changes so other models pick them up
|
|
550
|
+
if [ -d "$LEDGER_DIR/.git" ]; then
|
|
551
|
+
cd "$LEDGER_DIR"
|
|
552
|
+
git add -A 2>/dev/null
|
|
553
|
+
git commit -m "session handoff $(date -u +%Y-%m-%dT%H:%M:%SZ)" --no-verify 2>/dev/null
|
|
554
|
+
git push origin main 2>/dev/null &
|
|
555
|
+
fi
|
|
556
|
+
|
|
557
|
+
# Save session timestamp
|
|
558
|
+
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$DELIMIT_HOME/.last_session_end"
|
|
559
|
+
echo "[Delimit] Session context saved."
|
|
560
|
+
`;
|
|
561
|
+
fs.writeFileSync(stopScript, stopContent);
|
|
562
|
+
fs.chmodSync(stopScript, '755');
|
|
563
|
+
|
|
564
|
+
const existingStop = config.hooks.Stop.find(group => {
|
|
565
|
+
const cmds = (group.hooks || []).map(h => h.command || '');
|
|
566
|
+
return cmds.some(c => c.includes('delimit'));
|
|
567
|
+
});
|
|
568
|
+
if (!existingStop) {
|
|
569
|
+
config.hooks.Stop.push({
|
|
570
|
+
matcher: '',
|
|
571
|
+
hooks: [{
|
|
572
|
+
type: 'command',
|
|
573
|
+
command: stopScript,
|
|
574
|
+
timeout: 10,
|
|
575
|
+
}],
|
|
576
|
+
});
|
|
577
|
+
changes.push('Stop');
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
533
581
|
// Write hooks to all target settings files
|
|
534
582
|
const configJson = JSON.stringify(config, null, 2);
|
|
535
583
|
for (const target of writeTargets) {
|
|
@@ -686,7 +734,7 @@ function removeClaudeHooks() {
|
|
|
686
734
|
|
|
687
735
|
let changed = false;
|
|
688
736
|
|
|
689
|
-
for (const event of ['SessionStart', 'PreToolUse', 'PostToolUse']) {
|
|
737
|
+
for (const event of ['SessionStart', 'PreToolUse', 'PostToolUse', 'Stop']) {
|
|
690
738
|
if (Array.isArray(config.hooks[event])) {
|
|
691
739
|
const before = config.hooks[event].length;
|
|
692
740
|
config.hooks[event] = config.hooks[event].filter(h => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "delimit-cli",
|
|
3
3
|
"mcpName": "io.github.delimit-ai/delimit-mcp-server",
|
|
4
|
-
"version": "4.1.
|
|
4
|
+
"version": "4.1.32",
|
|
5
5
|
"description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|