delimit-cli 3.11.10 → 3.12.0

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 CHANGED
@@ -1,5 +1,67 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.12.0] - 2026-03-26
4
+
5
+ ### Added
6
+ - Cross-model hook system: session-start, pre-tool, and pre-commit hooks for Claude Code, Codex, and Gemini CLI
7
+ - `delimit export` and `delimit import` commands for shareable governance config
8
+ - `delimit hook <event>` commands for manual hook invocation
9
+ - `delimit uninstall` removes hooks from all AI tools cleanly
10
+ - Pre-push hooks for catching governance violations before remote push
11
+ - Cursor and Codex adapters for native integration
12
+
13
+ ### Changed
14
+ - "Keep Building." success message displayed on lint/diff/doctor pass
15
+ - Zero-config action improvements for smoother CI integration
16
+
17
+ ## [3.11.10] - 2026-03-24
18
+
19
+ ### Added
20
+ - Cursor adapter with `.cursor/rules/delimit.md` support (Cursor 0.45+)
21
+ - Codex adapters: `codex-skill.js` (governance) and `codex-security.js`
22
+ - Setup `--dry-run` previews config changes before writing
23
+ - Uninstall `--dry-run` with backups for all 4 AI assistants
24
+ - Post-install config validation (checks all assistant configs)
25
+ - npm publish workflow with provenance and approval gates
26
+ - Setup matrix tests (13 new tests across all AI assistants)
27
+
28
+ ### Changed
29
+ - Repo renamed to `delimit-ai/delimit-mcp-server`
30
+ - Description: "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate."
31
+ - Clear Free vs Pro tier boundaries in README
32
+
33
+ ### Security
34
+ - Hardened `.npmignore` (blocks .env, credentials, keys)
35
+ - Supply chain security section in SECURITY.md
36
+
37
+ ## [3.11.9] - 2026-03-23
38
+
39
+ ### Added
40
+ - Auto-detect API keys and CLIs on `delimit init` and `delimit version`
41
+ - `delimit_quickstart` MCP tool (60-second guided first-run)
42
+ - Deliberation cost tracking (estimate + actual per model)
43
+ - Input sanitization: `_sanitize_path`, `_sanitize_subprocess_arg`
44
+ - GitHub Action smoke test workflow
45
+
46
+ ### Fixed
47
+ - Gemini deliberation HTTP 400 (ADC credentials + jamsons project)
48
+ - Deliberation timeout: parallelized round 1 (46% faster)
49
+ - Sensor dedup: titles include repo/issue to prevent duplicates
50
+ - Test-mode guard prevents ledger pollution from tests
51
+
52
+ ### Changed
53
+ - Governance default mode: advisory -> guarded (blocks critical actions)
54
+ - Ledger tools now route through governance loop (113/116 tools)
55
+ - Dialogue deliberation capped at 4 rounds (was 6)
56
+ - Per-model API timeout: 120s -> 45s (fail fast)
57
+
58
+ ## [3.11.8] - 2026-03-23
59
+
60
+ ### Fixed
61
+ - Codex compatibility: 11 type coercion fixes (string->list/dict)
62
+ - CI green: FastMCP fallback, env var injection, mock patterns
63
+ - Dashboard dark/light mode with CSS variables
64
+
3
65
  ## [2.4.0] - 2026-03-15
4
66
 
5
67
  ### Added
package/README.md CHANGED
@@ -5,15 +5,21 @@ Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, govern
5
5
  [![npm](https://img.shields.io/npm/v/delimit-cli)](https://www.npmjs.com/package/delimit-cli)
6
6
  [![GitHub Action](https://img.shields.io/badge/GitHub%20Action-v1.6.0-blue)](https://github.com/marketplace/actions/delimit-api-governance)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
- [![delimit MCP server](https://glama.ai/mcp/servers/delimit-ai/delimit/badges/score.svg)](https://glama.ai/mcp/servers/delimit-ai/delimit)
8
+ [![Glama](https://glama.ai/mcp/servers/delimit-ai/delimit-mcp-server/badges/score.svg)](https://glama.ai/mcp/servers/delimit-ai/delimit-mcp-server)
9
9
 
10
- Your tasks, memory, and governance carry between Claude Code, Codex, and Gemini CLI. Persistent ledger, API breaking change detection, security audit, multi-model deliberation — all shared across assistants.
10
+ Persistent ledger, API governance, security orchestration, and multi-model deliberation — all shared across Claude Code, Codex, Cursor, and Gemini CLI.
11
11
 
12
12
  ---
13
13
 
14
14
  ## GitHub Action
15
15
 
16
- Add to any repo with an OpenAPI spec:
16
+ Zero-config auto-detects your OpenAPI spec:
17
+
18
+ ```yaml
19
+ - uses: delimit-ai/delimit-action@v1
20
+ ```
21
+
22
+ Or with full configuration:
17
23
 
18
24
  ```yaml
19
25
  name: API Contract Check
@@ -9,6 +9,7 @@ const chalk = require('chalk');
9
9
  const inquirer = require('inquirer');
10
10
  const DelimitAuthSetup = require('../lib/auth-setup');
11
11
  const DelimitHooksInstaller = require('../lib/hooks-installer');
12
+ const crossModelHooks = require('../lib/cross-model-hooks');
12
13
 
13
14
  const AGENT_URL = `http://127.0.0.1:${process.env.DELIMIT_AGENT_PORT || 7823}`;
14
15
  const program = new Command();
@@ -535,6 +536,36 @@ program
535
536
  changes.push({ target: '~/.delimit/shims/', action: 'Remove CLI shims directory' });
536
537
  }
537
538
 
539
+ // 8. Cross-model governance hooks (LED-202)
540
+ const claudeSettingsPath = path.join(HOME, '.claude', 'settings.json');
541
+ if (fs.existsSync(claudeSettingsPath)) {
542
+ try {
543
+ const cfg = JSON.parse(fs.readFileSync(claudeSettingsPath, 'utf8'));
544
+ if (cfg.hooks) {
545
+ const hasDelimitHook = Object.values(cfg.hooks).some(arr =>
546
+ Array.isArray(arr) && arr.some(h => h.command && h.command.includes('delimit-cli'))
547
+ );
548
+ if (hasDelimitHook) {
549
+ changes.push({ target: '~/.claude/settings.json', action: 'Remove Delimit governance hooks' });
550
+ }
551
+ }
552
+ } catch (e) {}
553
+ }
554
+ const codexInstructions = path.join(HOME, '.codex', 'instructions.md');
555
+ if (fs.existsSync(codexInstructions)) {
556
+ const content = fs.readFileSync(codexInstructions, 'utf8');
557
+ if (content.includes('delimit:hooks-start')) {
558
+ changes.push({ target: '~/.codex/instructions.md', action: 'Remove Delimit hook instructions' });
559
+ }
560
+ }
561
+ const geminiGovMd = path.join(HOME, '.gemini', 'GEMINI.md');
562
+ if (fs.existsSync(geminiGovMd)) {
563
+ const content = fs.readFileSync(geminiGovMd, 'utf8');
564
+ if (content.includes('Delimit Governance')) {
565
+ changes.push({ target: '~/.gemini/GEMINI.md', action: 'Remove Delimit governance file' });
566
+ }
567
+ }
568
+
538
569
  if (changes.length === 0) {
539
570
  console.log(chalk.green('\nNo Delimit integrations found. Nothing to remove.\n'));
540
571
  return;
@@ -625,6 +656,14 @@ program
625
656
  } catch (e) {}
626
657
  }
627
658
 
659
+ // Remove cross-model governance hooks (LED-202)
660
+ try {
661
+ const removedFrom = crossModelHooks.removeAllHooks();
662
+ if (removedFrom.length > 0) {
663
+ console.log(chalk.green(`✓ Removed governance hooks from: ${removedFrom.join(', ')}`));
664
+ }
665
+ } catch (e) { /* cross-model-hooks module not critical */ }
666
+
628
667
  console.log(chalk.green('\n Delimit has been completely removed.'));
629
668
  console.log(chalk.gray(` Backups saved to: ${backupDir}`));
630
669
  console.log(chalk.gray(' Your data in ~/.delimit/ has been preserved.'));
@@ -701,95 +740,86 @@ program
701
740
  await proxyAITool(tool, args);
702
741
  });
703
742
 
704
- // Hook handler (called by Git hooks)
705
- program
706
- .command('hook <type>')
707
- .description('Internal hook handler')
743
+ // ---------------------------------------------------------------------------
744
+ // LED-202: Cross-model hook commands
745
+ // ---------------------------------------------------------------------------
708
746
 
709
- .action(async (type) => {
710
- await ensureAgent();
711
-
712
- // Gather context
713
- const context = {
714
- command: type,
715
- pwd: process.cwd(),
716
- gitBranch: 'unknown',
717
- files: [],
718
- diff: ''
719
- };
720
-
721
- // Try to get Git info, but don't fail if not in repo
747
+ const hookCmd = program
748
+ .command('hook <event> [tool_name]')
749
+ .description('Governance hook handler (session-start | pre-tool | pre-commit)')
750
+ .action(async (event, toolName) => {
722
751
  try {
723
- context.gitBranch = execSync('git branch --show-current 2>/dev/null').toString().trim() || 'unknown';
724
- } catch (e) {
725
- // Not in a Git repo or Git not available
726
- context.gitBranch = 'unknown';
727
- }
728
-
729
- if (type === 'pre-commit') {
730
- try {
731
- context.files = execSync('git diff --cached --name-only 2>/dev/null').toString().split('\n').filter(f => f);
732
- context.diff = execSync('git diff --cached 2>/dev/null').toString();
733
- } catch (e) {
734
- // Not in a Git repo or no staged changes
735
- context.files = [];
736
- context.diff = '';
737
- }
738
- } else if (type === 'pre-push') {
739
- try {
740
- // Get commits to be pushed
741
- context.files = execSync('git diff --name-only @{upstream}...HEAD 2>/dev/null').toString().split('\n').filter(f => f);
742
- context.diff = execSync('git diff @{upstream}...HEAD 2>/dev/null').toString();
743
- } catch (e) {
744
- // No upstream or not in repo
745
- context.files = [];
746
- context.diff = '';
747
- }
748
- }
749
-
750
- // Query agent for decision
751
- const { data: decision } = await axios.post(`${AGENT_URL}/evaluate`, context);
752
-
753
- // Display decision
754
- if (decision.message) {
755
- const color = decision.action === 'block' ? chalk.red :
756
- decision.action === 'prompt' ? chalk.yellow :
757
- chalk.blue;
758
- console.log(color(decision.message));
759
- }
760
-
761
- // Handle the decision
762
- if (decision.action === 'block') {
763
- if (decision.requiresOverride) {
764
- console.log(chalk.red('Action blocked. Cannot override in enforce mode.'));
765
- process.exit(1);
766
- } else {
767
- const { override } = await inquirer.prompt([{
768
- type: 'confirm',
769
- name: 'override',
770
- message: 'Override and continue?',
771
- default: false
772
- }]);
773
-
774
- if (!override) {
775
- process.exit(1);
776
- }
752
+ switch (event) {
753
+ case 'session-start':
754
+ await crossModelHooks.hookSessionStart();
755
+ break;
756
+ case 'pre-tool':
757
+ await crossModelHooks.hookPreTool(toolName || 'unknown');
758
+ break;
759
+ case 'pre-commit':
760
+ await crossModelHooks.hookPreCommit();
761
+ break;
762
+ default:
763
+ // Legacy: fall back to agent-based hook evaluation
764
+ await ensureAgent();
765
+ const context = {
766
+ command: event,
767
+ pwd: process.cwd(),
768
+ gitBranch: 'unknown',
769
+ files: [],
770
+ diff: ''
771
+ };
772
+ try {
773
+ context.gitBranch = execSync('git branch --show-current 2>/dev/null').toString().trim() || 'unknown';
774
+ } catch (e) { context.gitBranch = 'unknown'; }
775
+
776
+ if (event === 'pre-push') {
777
+ try {
778
+ context.files = execSync('git diff --name-only @{upstream}...HEAD 2>/dev/null').toString().split('\n').filter(f => f);
779
+ context.diff = execSync('git diff @{upstream}...HEAD 2>/dev/null').toString();
780
+ } catch (e) {
781
+ context.files = [];
782
+ context.diff = '';
783
+ }
784
+ }
785
+
786
+ const { data: decision } = await axios.post(`${AGENT_URL}/evaluate`, context);
787
+ if (decision.message) {
788
+ const color = decision.action === 'block' ? chalk.red :
789
+ decision.action === 'prompt' ? chalk.yellow :
790
+ chalk.blue;
791
+ console.log(color(decision.message));
792
+ }
793
+ if (decision.action === 'block') {
794
+ if (decision.requiresOverride) {
795
+ console.log(chalk.red('Action blocked. Cannot override in enforce mode.'));
796
+ process.exit(1);
797
+ } else {
798
+ const { override } = await inquirer.prompt([{
799
+ type: 'confirm',
800
+ name: 'override',
801
+ message: 'Override and continue?',
802
+ default: false
803
+ }]);
804
+ if (!override) process.exit(1);
805
+ }
806
+ } else if (decision.action === 'prompt') {
807
+ const { proceed } = await inquirer.prompt([{
808
+ type: 'confirm',
809
+ name: 'proceed',
810
+ message: 'Continue with this action?',
811
+ default: false
812
+ }]);
813
+ if (!proceed) process.exit(1);
814
+ }
815
+ process.exit(0);
777
816
  }
778
- } else if (decision.action === 'prompt') {
779
- const { proceed } = await inquirer.prompt([{
780
- type: 'confirm',
781
- name: 'proceed',
782
- message: 'Continue with this action?',
783
- default: false
784
- }]);
785
-
786
- if (!proceed) {
787
- process.exit(1);
817
+ } catch (err) {
818
+ // Hooks must never block the AI tool -- fail open
819
+ if (process.env.DELIMIT_DEBUG) {
820
+ process.stderr.write(`[Delimit] Hook error: ${err.message}\n`);
788
821
  }
789
822
  }
790
-
791
- // Action allowed
792
- process.exit(0);
793
823
  });
794
824
 
795
825
  // ═══════════════════════════════════════════════════════════════════════
@@ -1135,6 +1165,7 @@ program
1135
1165
  console.log('');
1136
1166
  if (fail === 0 && warn === 0) {
1137
1167
  console.log(chalk.green.bold(' All checks passed! Ready to lint.\n'));
1168
+ console.log('Keep Building.\n');
1138
1169
  } else if (fail === 0) {
1139
1170
  console.log(chalk.yellow.bold(` ${ok} passed, ${warn} warning(s). Setup looks good.\n`));
1140
1171
  } else {
@@ -1263,6 +1294,10 @@ program
1263
1294
  console.log('');
1264
1295
  }
1265
1296
 
1297
+ if (decision === 'pass') {
1298
+ console.log('Keep Building.\n');
1299
+ }
1300
+
1266
1301
  process.exit(result.exit_code || 0);
1267
1302
  } catch (err) {
1268
1303
  console.error(chalk.red(`Error: ${err.message}`));
@@ -1295,6 +1330,10 @@ program
1295
1330
  console.log(` ${tag} ${c.message}`);
1296
1331
  });
1297
1332
  console.log('');
1333
+
1334
+ if (result.breaking_changes === 0) {
1335
+ console.log('Keep Building.\n');
1336
+ }
1298
1337
  } catch (err) {
1299
1338
  console.error(chalk.red(`Error: ${err.message}`));
1300
1339
  process.exit(1);
@@ -1428,6 +1467,187 @@ program
1428
1467
  console.log(chalk.dim('Tier: pro'));
1429
1468
  });
1430
1469
 
1470
+ // ---------------------------------------------------------------------------
1471
+ // LED-187: Export governance config as shareable JSON
1472
+ // ---------------------------------------------------------------------------
1473
+
1474
+ /**
1475
+ * Build a governance config bundle from the current project directory.
1476
+ * Returns a plain object ready for JSON serialization.
1477
+ */
1478
+ function buildConfigBundle(cwd) {
1479
+ const bundle = {
1480
+ delimit_config_version: 1,
1481
+ created_at: new Date().toISOString(),
1482
+ project: path.basename(cwd),
1483
+ policies: null,
1484
+ workflow: null,
1485
+ };
1486
+
1487
+ // Read delimit.yml or .delimit/policies.yml
1488
+ const candidates = [
1489
+ path.join(cwd, 'delimit.yml'),
1490
+ path.join(cwd, '.delimit.yml'),
1491
+ path.join(cwd, '.delimit', 'policies.yml'),
1492
+ ];
1493
+ for (const p of candidates) {
1494
+ if (fs.existsSync(p)) {
1495
+ const raw = fs.readFileSync(p, 'utf-8');
1496
+ bundle.policies = { path: path.relative(cwd, p), content: raw };
1497
+ break;
1498
+ }
1499
+ }
1500
+
1501
+ // Read GitHub Action workflow if it exists
1502
+ const workflowPath = path.join(cwd, '.github', 'workflows', 'api-governance.yml');
1503
+ if (fs.existsSync(workflowPath)) {
1504
+ bundle.workflow = {
1505
+ path: '.github/workflows/api-governance.yml',
1506
+ content: fs.readFileSync(workflowPath, 'utf-8'),
1507
+ };
1508
+ }
1509
+
1510
+ return bundle;
1511
+ }
1512
+
1513
+ program
1514
+ .command('export')
1515
+ .description('Export governance config as shareable JSON')
1516
+ .option('-o, --output <file>', 'Write to file instead of stdout')
1517
+ .option('--url', 'Generate a delimit.ai/import share URL')
1518
+ .action(async (options) => {
1519
+ const cwd = process.cwd();
1520
+ const bundle = buildConfigBundle(cwd);
1521
+
1522
+ if (!bundle.policies) {
1523
+ console.error(chalk.red('No governance config found. Run "delimit init" first.'));
1524
+ process.exit(1);
1525
+ }
1526
+
1527
+ const json = JSON.stringify(bundle, null, 2);
1528
+
1529
+ if (options.url) {
1530
+ const encoded = Buffer.from(json).toString('base64');
1531
+ const shareUrl = `https://delimit.ai/import?config=${encoded}`;
1532
+ console.log(chalk.green('Share URL:\n'));
1533
+ console.log(shareUrl);
1534
+ return;
1535
+ }
1536
+
1537
+ if (options.output) {
1538
+ fs.writeFileSync(options.output, json);
1539
+ console.log(chalk.green(`Exported config to ${options.output}`));
1540
+ } else {
1541
+ console.log(json);
1542
+ }
1543
+ });
1544
+
1545
+ // ---------------------------------------------------------------------------
1546
+ // LED-187: Import governance config from file, URL, or base64 string
1547
+ // ---------------------------------------------------------------------------
1548
+
1549
+ /**
1550
+ * Parse a config bundle from various sources: file path, base64 string,
1551
+ * or a delimit.ai/import?config=... URL.
1552
+ */
1553
+ function parseConfigSource(source) {
1554
+ // URL form — extract base64 from query param
1555
+ if (source.startsWith('http://') || source.startsWith('https://')) {
1556
+ const url = new URL(source);
1557
+ const encoded = url.searchParams.get('config');
1558
+ if (!encoded) {
1559
+ throw new Error('URL does not contain a config= parameter');
1560
+ }
1561
+ return JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8'));
1562
+ }
1563
+
1564
+ // File path
1565
+ if (fs.existsSync(source)) {
1566
+ return JSON.parse(fs.readFileSync(source, 'utf-8'));
1567
+ }
1568
+
1569
+ // Assume base64
1570
+ try {
1571
+ return JSON.parse(Buffer.from(source, 'base64').toString('utf-8'));
1572
+ } catch {
1573
+ throw new Error('Could not parse source as file path, URL, or base64');
1574
+ }
1575
+ }
1576
+
1577
+ program
1578
+ .command('import <source>')
1579
+ .description('Import governance config from file, URL, or base64 string')
1580
+ .option('--action', 'Also write the GitHub Action workflow')
1581
+ .option('--yes', 'Skip confirmation prompt')
1582
+ .action(async (source, options) => {
1583
+ let bundle;
1584
+ try {
1585
+ bundle = parseConfigSource(source);
1586
+ } catch (err) {
1587
+ console.error(chalk.red(`Failed to parse config: ${err.message}`));
1588
+ process.exit(1);
1589
+ }
1590
+
1591
+ if (!bundle.policies || !bundle.policies.content) {
1592
+ console.error(chalk.red('Invalid config bundle: missing policies'));
1593
+ process.exit(1);
1594
+ }
1595
+
1596
+ const cwd = process.cwd();
1597
+ const policyDest = path.join(cwd, bundle.policies.path || 'delimit.yml');
1598
+
1599
+ // Show what will change
1600
+ console.log(chalk.blue.bold('\nConfig Import Preview\n'));
1601
+ console.log(` Project: ${chalk.bold(bundle.project || 'unknown')}`);
1602
+ console.log(` Created: ${bundle.created_at || 'unknown'}`);
1603
+ console.log(` Policy file: ${chalk.bold(policyDest)}`);
1604
+ if (options.action && bundle.workflow) {
1605
+ console.log(` Workflow: ${chalk.bold(path.join(cwd, bundle.workflow.path))}`);
1606
+ }
1607
+
1608
+ // Show diff if policy file already exists
1609
+ if (fs.existsSync(policyDest)) {
1610
+ const existing = fs.readFileSync(policyDest, 'utf-8');
1611
+ if (existing === bundle.policies.content) {
1612
+ console.log(chalk.yellow('\n No changes -- imported config matches current config.'));
1613
+ return;
1614
+ }
1615
+ console.log(chalk.yellow('\n Policy file already exists and will be overwritten.'));
1616
+ }
1617
+
1618
+ console.log('');
1619
+
1620
+ if (!options.yes) {
1621
+ const { confirm } = await inquirer.prompt([{
1622
+ type: 'confirm',
1623
+ name: 'confirm',
1624
+ message: 'Apply this config?',
1625
+ default: false,
1626
+ }]);
1627
+ if (!confirm) {
1628
+ console.log(chalk.red('Import cancelled'));
1629
+ return;
1630
+ }
1631
+ }
1632
+
1633
+ // Write policy file
1634
+ const policyDir = path.dirname(policyDest);
1635
+ fs.mkdirSync(policyDir, { recursive: true });
1636
+ fs.writeFileSync(policyDest, bundle.policies.content);
1637
+ console.log(chalk.green(` Created ${policyDest}`));
1638
+
1639
+ // Optionally write workflow
1640
+ if (options.action && bundle.workflow && bundle.workflow.content) {
1641
+ const workflowDest = path.join(cwd, bundle.workflow.path);
1642
+ const wfDir = path.dirname(workflowDest);
1643
+ fs.mkdirSync(wfDir, { recursive: true });
1644
+ fs.writeFileSync(workflowDest, bundle.workflow.content);
1645
+ console.log(chalk.green(` Created ${workflowDest}`));
1646
+ }
1647
+
1648
+ console.log(chalk.green('\nConfig imported successfully.'));
1649
+ });
1650
+
1431
1651
  // Version subcommand alias (users type 'delimit version' not 'delimit -V')
1432
1652
  program
1433
1653
  .command('version')
@@ -54,7 +54,7 @@ async function main() {
54
54
  log(blue(' / / / / __/ / / / // /|_/ // / / / '));
55
55
  log(blue(' / /_/ / /___/ /____/ // / / // / / / '));
56
56
  log(blue('/_____/_____/_____/___/_/ /_/___/ /_/ '));
57
- log(dim(' One workspace for every AI coding assistant'));
57
+ log(dim(' Unify all AI coding assistants'));
58
58
  log('');
59
59
 
60
60
  // Step 1: Check prerequisites
@@ -567,8 +567,56 @@ echo "[Delimit] ${toolName} not found" >&2; exit 127
567
567
  }
568
568
  log('');
569
569
 
570
- // Step 7: Local dashboard API server
571
- step(7, 'Local dashboard API...');
570
+ // Step 7: Install cross-model governance hooks (LED-202)
571
+ step(7, 'Installing AI assistant hooks...');
572
+
573
+ try {
574
+ const crossModelHooks = require('../lib/cross-model-hooks');
575
+ const hookConfig = crossModelHooks.loadHookConfig();
576
+ const detected = crossModelHooks.detectAITools();
577
+
578
+ if (detected.length === 0) {
579
+ log(` ${dim(' No AI assistants detected -- hooks will be installed when tools are found')}`);
580
+ } else {
581
+ log(` ${dim(' Detected: ' + detected.map(t => t.name).join(', '))}`);
582
+
583
+ // Install hooks (auto-accept in non-interactive or prompt if TTY)
584
+ let installHooks = true;
585
+ const inq = (() => { try { return require('inquirer'); } catch { return null; } })();
586
+ if (inq && process.stdin.isTTY) {
587
+ try {
588
+ const answer = await inq.prompt([{
589
+ type: 'confirm',
590
+ name: 'install',
591
+ message: `Install governance hooks for ${detected.map(t => t.name).join(', ')}?`,
592
+ default: true,
593
+ }]);
594
+ installHooks = answer.install;
595
+ } catch {
596
+ installHooks = true;
597
+ }
598
+ }
599
+
600
+ if (installHooks) {
601
+ for (const tool of detected) {
602
+ const result = crossModelHooks.installHooksForTool(tool, hookConfig);
603
+ if (result.changes.length > 0) {
604
+ log(` ${green('✓')} ${tool.name}: ${result.changes.join(', ')}`);
605
+ } else {
606
+ log(` ${dim(' ' + tool.name + ': hooks already installed')}`);
607
+ }
608
+ }
609
+ } else {
610
+ log(` ${dim(' Skipped. Install later: delimit hook install')}`);
611
+ }
612
+ }
613
+ } catch (e) {
614
+ log(` ${dim(' Hook installation skipped: ' + e.message)}`);
615
+ }
616
+ log('');
617
+
618
+ // Step 8: Local dashboard API server
619
+ step(8, 'Local dashboard API...');
572
620
 
573
621
  const localServerPath = path.join(DELIMIT_HOME, 'server', 'ai', 'local_server.py');
574
622
  if (fs.existsSync(localServerPath)) {
@@ -580,8 +628,8 @@ echo "[Delimit] ${toolName} not found" >&2; exit 127
580
628
  }
581
629
  log('');
582
630
 
583
- // Step 8: Post-install config validation (LED-098)
584
- step(8, 'Validating config integrity...');
631
+ // Step 9: Post-install config validation (LED-098)
632
+ step(9, 'Validating config integrity...');
585
633
 
586
634
  let validationIssues = 0;
587
635
  const configFiles = [
@@ -670,7 +718,7 @@ echo "[Delimit] ${toolName} not found" >&2; exit 127
670
718
  log('');
671
719
 
672
720
  // Step 9: Done
673
- step(9, 'Done!');
721
+ step(10, 'Done!');
674
722
  log('');
675
723
  log(` ${green('Delimit is installed.')} Your AI now has persistent memory and governance.`);
676
724
  log('');
@@ -707,7 +755,7 @@ function getDelimitSection() {
707
755
  return `<!-- delimit:start v${version} -->
708
756
  # Delimit
709
757
 
710
- One workspace for every AI coding assistant.
758
+ Unify all AI coding assistants with persistent context, governance, and multi-model debate.
711
759
 
712
760
  ## On every session start:
713
761
  1. Call \`delimit_ledger_context\` to check for open tasks
@@ -756,7 +804,7 @@ Add breaking change detection to any repo:
756
804
 
757
805
  ## Links
758
806
  - Docs: https://delimit.ai/docs
759
- - GitHub: https://github.com/delimit-ai/delimit
807
+ - GitHub: https://github.com/delimit-ai/delimit-mcp-server
760
808
  - Action: https://github.com/marketplace/actions/delimit-api-governance
761
809
  <!-- delimit:end -->`;
762
810
  }