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 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
@@ -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')
@@ -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.30",
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": [