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 +62 -0
- package/README.md +9 -3
- package/bin/delimit-cli.js +304 -84
- package/bin/delimit-setup.js +56 -8
- package/lib/cross-model-hooks.js +706 -0
- package/package.json +2 -2
- package/server.json +2 -2
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
|
[](https://www.npmjs.com/package/delimit-cli)
|
|
6
6
|
[](https://github.com/marketplace/actions/delimit-api-governance)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
|
-
[](https://glama.ai/mcp/servers/delimit-ai/delimit-mcp-server)
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
package/bin/delimit-cli.js
CHANGED
|
@@ -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
|
-
//
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
.description('Internal hook handler')
|
|
743
|
+
// ---------------------------------------------------------------------------
|
|
744
|
+
// LED-202: Cross-model hook commands
|
|
745
|
+
// ---------------------------------------------------------------------------
|
|
708
746
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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
|
-
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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')
|
package/bin/delimit-setup.js
CHANGED
|
@@ -54,7 +54,7 @@ async function main() {
|
|
|
54
54
|
log(blue(' / / / / __/ / / / // /|_/ // / / / '));
|
|
55
55
|
log(blue(' / /_/ / /___/ /____/ // / / // / / / '));
|
|
56
56
|
log(blue('/_____/_____/_____/___/_/ /_/___/ /_/ '));
|
|
57
|
-
log(dim('
|
|
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:
|
|
571
|
-
step(7, '
|
|
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
|
|
584
|
-
step(
|
|
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(
|
|
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
|
-
|
|
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
|
}
|