delimit-cli 3.12.0 → 3.12.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.
@@ -1648,6 +1648,118 @@ program
1648
1648
  console.log(chalk.green('\nConfig imported successfully.'));
1649
1649
  });
1650
1650
 
1651
+ // Deliberate command -- entry point for cross-model deliberation
1652
+ program
1653
+ .command('deliberate [question...]')
1654
+ .description('Deliberate on a strategic question using cross-model consensus')
1655
+ .option('--list', 'List pending strategy items from the ledger')
1656
+ .option('--mode <mode>', 'Deliberation mode: quick | dialogue | debate', 'dialogue')
1657
+ .option('--question <q>', 'Question to deliberate (alternative to positional arg)')
1658
+ .action(async (questionParts, options) => {
1659
+ const question = options.question || (questionParts.length > 0 ? questionParts.join(' ') : null);
1660
+
1661
+ if (options.list) {
1662
+ // List pending strategy items
1663
+ const count = crossModelHooks.countPendingStrategyItems();
1664
+ if (count === 0) {
1665
+ console.log(chalk.green('No pending strategy items in the ledger.'));
1666
+ } else {
1667
+ console.log(chalk.blue(`${count} strategic decision${count === 1 ? '' : 's'} pending deliberation.`));
1668
+ const top = crossModelHooks.getTopStrategyItem();
1669
+ if (top) {
1670
+ console.log('');
1671
+ console.log(chalk.bold('Highest priority:'));
1672
+ console.log(` ${top.id || 'unknown'}: ${top.title || top.description || 'No title'}`);
1673
+ if (top.priority) console.log(` Priority: ${top.priority}`);
1674
+ }
1675
+ }
1676
+ return;
1677
+ }
1678
+
1679
+ if (question) {
1680
+ console.log(chalk.blue.bold('\nDelimit Deliberation\n'));
1681
+ console.log(`Question: ${chalk.bold(question)}\n`);
1682
+
1683
+ // Try to run deliberation directly via the gateway
1684
+ const HOME = process.env.HOME || require('os').homedir();
1685
+ const gatewayScript = path.join(HOME, '.delimit', 'server', 'ai', 'deliberation.py');
1686
+ const gatewayAlt = '/home/delimit/delimit-gateway/ai/deliberation.py';
1687
+ const scriptPath = fs.existsSync(gatewayScript) ? gatewayScript : fs.existsSync(gatewayAlt) ? gatewayAlt : null;
1688
+
1689
+ if (scriptPath) {
1690
+ console.log(chalk.dim('Running multi-model deliberation...\n'));
1691
+ try {
1692
+ const escapedQ = question.replace(/'/g, "\\'");
1693
+ const pyCmd = `python3 -c "
1694
+ import sys, os, json
1695
+ sys.path.insert(0, os.path.dirname('${scriptPath}'))
1696
+ os.chdir(os.path.dirname('${scriptPath}'))
1697
+ from deliberation import run_deliberation
1698
+ result = run_deliberation('${escapedQ}', mode='${options.mode}', max_rounds=2)
1699
+ if result.get('verdict'):
1700
+ print('VERDICT:', result['verdict'])
1701
+ if result.get('confidence'):
1702
+ print('CONFIDENCE:', result['confidence'])
1703
+ if result.get('summary'):
1704
+ print()
1705
+ print(result['summary'])
1706
+ "`;
1707
+ const result = execSync(pyCmd, {
1708
+ encoding: 'utf-8',
1709
+ timeout: 120000,
1710
+ env: { ...process.env, PYTHONPATH: path.dirname(scriptPath) },
1711
+ });
1712
+ console.log(result);
1713
+ } catch (e) {
1714
+ // Fallback: guide user to MCP tool
1715
+ console.log(chalk.yellow('Direct deliberation unavailable. Use the MCP tool instead:\n'));
1716
+ console.log(chalk.bold('In your AI assistant (Claude Code, Codex, or Gemini CLI):'));
1717
+ console.log(` ${chalk.cyan(`delimit_deliberate: ${question}`)}\n`);
1718
+ }
1719
+ } else {
1720
+ console.log('To deliberate, use one of the following approaches:\n');
1721
+ console.log(chalk.bold('1. In your AI assistant (Claude Code, Codex, or Gemini CLI):'));
1722
+ console.log(` ${chalk.cyan(`delimit_deliberate: ${question}`)}\n`);
1723
+ console.log(chalk.bold('2. Using the MCP tool directly:'));
1724
+ console.log(` ${chalk.cyan(`Call delimit_deliberate with question="${question}"`)}\n`);
1725
+ }
1726
+
1727
+ // Save pending deliberation to file for reference
1728
+ const deliberationDir = path.join(HOME, '.delimit', 'deliberation');
1729
+ fs.mkdirSync(deliberationDir, { recursive: true });
1730
+ const pending = {
1731
+ question,
1732
+ mode: options.mode,
1733
+ created: new Date().toISOString(),
1734
+ status: 'pending',
1735
+ };
1736
+ fs.writeFileSync(
1737
+ path.join(deliberationDir, 'pending.json'),
1738
+ JSON.stringify(pending, null, 2)
1739
+ );
1740
+ console.log(chalk.dim(`\nSaved to ~/.delimit/deliberation/pending.json`));
1741
+ } else {
1742
+ // No question -- check the ledger for the top strategy item
1743
+ const top = crossModelHooks.getTopStrategyItem();
1744
+ if (top) {
1745
+ const topQuestion = top.title || top.description || 'No description';
1746
+ console.log(chalk.blue.bold('\nDelimit Deliberation\n'));
1747
+ console.log(`Top pending strategy item: ${chalk.bold(topQuestion)}\n`);
1748
+ console.log('To deliberate on this item, use one of the following:\n');
1749
+ console.log(chalk.bold('1. In your AI assistant:'));
1750
+ console.log(` ${chalk.cyan(`delimit_deliberate: ${topQuestion}`)}\n`);
1751
+ console.log(chalk.bold('2. Using the MCP tool directly:'));
1752
+ console.log(` ${chalk.cyan(`Call delimit_deliberate with question="${topQuestion}"`)}\n`);
1753
+ } else {
1754
+ console.log(chalk.blue.bold('\nDelimit Deliberation\n'));
1755
+ console.log('No pending strategy items in the ledger.\n');
1756
+ console.log('To start a new deliberation:\n');
1757
+ console.log(` ${chalk.cyan('delimit deliberate "Should we adopt versioned API contracts?"')}\n`);
1758
+ console.log('Or ask your AI assistant to call the delimit_deliberate MCP tool.');
1759
+ }
1760
+ }
1761
+ });
1762
+
1651
1763
  // Version subcommand alias (users type 'delimit version' not 'delimit -V')
1652
1764
  program
1653
1765
  .command('version')
@@ -475,6 +475,75 @@ function removeAllHooks() {
475
475
  return results;
476
476
  }
477
477
 
478
+ // ---------------------------------------------------------------------------
479
+ // Deliberation helpers
480
+ // ---------------------------------------------------------------------------
481
+
482
+ /**
483
+ * Count pending strategy items in the ledger that have priority P0.
484
+ * Returns the count of open/in_progress P0 strategy items.
485
+ */
486
+ function countPendingStrategyItems() {
487
+ const ledgerDir = path.join(getDelimitHome(), 'ledger');
488
+ if (!fs.existsSync(ledgerDir)) return 0;
489
+
490
+ let count = 0;
491
+ try {
492
+ const files = fs.readdirSync(ledgerDir).filter(f => f.endsWith('.json'));
493
+ for (const f of files) {
494
+ try {
495
+ const items = JSON.parse(fs.readFileSync(path.join(ledgerDir, f), 'utf-8'));
496
+ if (!Array.isArray(items)) continue;
497
+ for (const item of items) {
498
+ const isOpen = item.status === 'open' || item.status === 'in_progress';
499
+ const isStrategy = item.category === 'strategy' || item.category === 'deliberation';
500
+ const isP0 = item.priority === 'P0' || item.priority === 0;
501
+ if (isOpen && (isStrategy || isP0)) {
502
+ count++;
503
+ }
504
+ }
505
+ } catch { /* ignore individual file parse errors */ }
506
+ }
507
+ } catch { /* ignore directory read errors */ }
508
+
509
+ return count;
510
+ }
511
+
512
+ /**
513
+ * Get the highest priority pending strategy item from the ledger.
514
+ * Returns the item object or null if none found.
515
+ */
516
+ function getTopStrategyItem() {
517
+ const ledgerDir = path.join(getDelimitHome(), 'ledger');
518
+ if (!fs.existsSync(ledgerDir)) return null;
519
+
520
+ let best = null;
521
+ const priorityOrder = { P0: 0, P1: 1, P2: 2, P3: 3 };
522
+
523
+ try {
524
+ const files = fs.readdirSync(ledgerDir).filter(f => f.endsWith('.json'));
525
+ for (const f of files) {
526
+ try {
527
+ const items = JSON.parse(fs.readFileSync(path.join(ledgerDir, f), 'utf-8'));
528
+ if (!Array.isArray(items)) continue;
529
+ for (const item of items) {
530
+ const isOpen = item.status === 'open' || item.status === 'in_progress';
531
+ const isStrategy = item.category === 'strategy' || item.category === 'deliberation';
532
+ const isP0 = item.priority === 'P0' || item.priority === 0;
533
+ if (isOpen && (isStrategy || isP0)) {
534
+ const rank = typeof item.priority === 'number' ? item.priority : (priorityOrder[item.priority] ?? 99);
535
+ if (!best || rank < (typeof best.priority === 'number' ? best.priority : (priorityOrder[best.priority] ?? 99))) {
536
+ best = item;
537
+ }
538
+ }
539
+ }
540
+ } catch { /* ignore */ }
541
+ }
542
+ } catch { /* ignore */ }
543
+
544
+ return best;
545
+ }
546
+
478
547
  // ---------------------------------------------------------------------------
479
548
  // Hook execution commands
480
549
  // ---------------------------------------------------------------------------
@@ -557,6 +626,14 @@ async function hookSessionStart() {
557
626
  }
558
627
  }
559
628
 
629
+ // Check for pending strategy items that need deliberation
630
+ if (config.show_strategy_items) {
631
+ const strategyCount = countPendingStrategyItems();
632
+ if (strategyCount > 0) {
633
+ lines.push(`[delimit] ${strategyCount} strategic decision${strategyCount === 1 ? '' : 's'} pending deliberation. Run: delimit deliberate`);
634
+ }
635
+ }
636
+
560
637
  // Git branch info
561
638
  try {
562
639
  const branch = execSync('git branch --show-current 2>/dev/null', { encoding: 'utf-8' }).trim();
@@ -679,6 +756,44 @@ async function hookPreCommit() {
679
756
  warnings.push('[Delimit] No governance policy found. Run "delimit init" to create one.');
680
757
  }
681
758
 
759
+ // Deliberation on API spec commits (opt-in via deliberate_on_commit)
760
+ if (config.deliberate_on_commit) {
761
+ try {
762
+ const stagedFiles2 = execSync('git diff --cached --name-only 2>/dev/null', {
763
+ encoding: 'utf-8',
764
+ timeout: 2000,
765
+ }).split('\n').filter(Boolean);
766
+
767
+ const apiSpecFiles = stagedFiles2.filter(f =>
768
+ /openapi|swagger/i.test(f) && /\.(yaml|yml|json)$/.test(f)
769
+ );
770
+
771
+ if (apiSpecFiles.length > 0) {
772
+ // Auto-deliberate: call Delimit gateway directly
773
+ if (config.deliberate_on_commit === 'auto') {
774
+ process.stderr.write('[delimit] API spec change detected — running multi-model deliberation...\n');
775
+ try {
776
+ const diff = execSync(`git diff --cached -- ${apiSpecFiles.join(' ')} 2>/dev/null`, {
777
+ encoding: 'utf-8',
778
+ timeout: 5000,
779
+ maxBuffer: 50 * 1024,
780
+ }).slice(0, 2000);
781
+ const question = `This commit modifies API specs (${apiSpecFiles.join(', ')}). Is this change safe to ship? Are there breaking changes?\n\nDiff:\n${diff}`;
782
+ const result = execSync(`npx delimit-cli deliberate --question "${question.replace(/"/g, '\\"')}" --mode quick 2>/dev/null`, {
783
+ encoding: 'utf-8',
784
+ timeout: 60000,
785
+ });
786
+ process.stderr.write(result + '\n');
787
+ } catch (e) {
788
+ warnings.push(`[delimit] Deliberation failed: ${e.message?.slice(0, 100) || 'timeout'}. Proceeding with commit.`);
789
+ }
790
+ } else {
791
+ warnings.push('[delimit] This commit modifies API specs. Consider running: delimit deliberate "Is this change safe?"');
792
+ }
793
+ }
794
+ } catch { /* not in git repo */ }
795
+ }
796
+
682
797
  if (warnings.length > 0) {
683
798
  process.stderr.write(warnings.join('\n') + '\n');
684
799
  }
@@ -703,4 +818,6 @@ module.exports = {
703
818
  hookSessionStart,
704
819
  hookPreTool,
705
820
  hookPreCommit,
821
+ countPendingStrategyItems,
822
+ getTopStrategyItem,
706
823
  };
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": "3.12.0",
4
+ "version": "3.12.1",
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": [