delimit-cli 2.3.0 ā 2.3.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.
- package/bin/delimit-cli.js +175 -119
- package/package.json +5 -5
package/bin/delimit-cli.js
CHANGED
|
@@ -49,8 +49,8 @@ async function ensureAgent() {
|
|
|
49
49
|
|
|
50
50
|
program
|
|
51
51
|
.name('delimit')
|
|
52
|
-
.description('
|
|
53
|
-
.version('2.
|
|
52
|
+
.description('Prevent breaking API changes before they reach production')
|
|
53
|
+
.version('2.3.0');
|
|
54
54
|
|
|
55
55
|
// Install command with modes
|
|
56
56
|
program
|
|
@@ -158,6 +158,7 @@ program
|
|
|
158
158
|
program
|
|
159
159
|
.command('mode [mode]')
|
|
160
160
|
.description('Switch governance mode (advisory, guarded, enforce, auto)')
|
|
161
|
+
|
|
161
162
|
.action(async (mode) => {
|
|
162
163
|
await ensureAgent();
|
|
163
164
|
|
|
@@ -181,6 +182,7 @@ program
|
|
|
181
182
|
program
|
|
182
183
|
.command('status')
|
|
183
184
|
.description('Show governance status')
|
|
185
|
+
|
|
184
186
|
.option('--verbose', 'Show detailed status')
|
|
185
187
|
.action(async (options) => {
|
|
186
188
|
const agentRunning = await checkAgent();
|
|
@@ -274,6 +276,7 @@ program
|
|
|
274
276
|
program
|
|
275
277
|
.command('policy')
|
|
276
278
|
.description('Manage governance policies')
|
|
279
|
+
|
|
277
280
|
.option('--init', 'Create example policy file')
|
|
278
281
|
.option('--validate', 'Validate policy syntax')
|
|
279
282
|
.action(async (options) => {
|
|
@@ -326,6 +329,7 @@ overrides:
|
|
|
326
329
|
program
|
|
327
330
|
.command('auth')
|
|
328
331
|
.description('Setup authentication and credentials for services')
|
|
332
|
+
|
|
329
333
|
.option('--all', 'Setup all available services')
|
|
330
334
|
.option('--github', 'Setup GitHub authentication')
|
|
331
335
|
.option('--ai', 'Setup AI tools authentication')
|
|
@@ -387,6 +391,7 @@ program
|
|
|
387
391
|
program
|
|
388
392
|
.command('audit')
|
|
389
393
|
.description('View governance audit log')
|
|
394
|
+
|
|
390
395
|
.option('--tail <n>', 'Show last N entries', '10')
|
|
391
396
|
.action(async (options) => {
|
|
392
397
|
await ensureAgent();
|
|
@@ -412,120 +417,14 @@ program
|
|
|
412
417
|
});
|
|
413
418
|
});
|
|
414
419
|
|
|
415
|
-
// Doctor command -
|
|
416
|
-
|
|
417
|
-
.command('doctor')
|
|
418
|
-
.description('Diagnose Delimit configuration and issues')
|
|
419
|
-
.action(async () => {
|
|
420
|
-
console.log(chalk.blue.bold('\n𩺠Delimit Doctor\n'));
|
|
421
|
-
const issues = [];
|
|
422
|
-
const warnings = [];
|
|
423
|
-
const info = [];
|
|
424
|
-
|
|
425
|
-
// Check agent status
|
|
426
|
-
const agentRunning = await checkAgent();
|
|
427
|
-
if (!agentRunning) {
|
|
428
|
-
issues.push('Agent is not running. Run "delimit status" to start it.');
|
|
429
|
-
} else {
|
|
430
|
-
info.push('Agent is running and responsive');
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Check Git hooks
|
|
434
|
-
try {
|
|
435
|
-
const hooksPath = execSync('git config --global core.hooksPath').toString().trim();
|
|
436
|
-
if (hooksPath.includes('.delimit')) {
|
|
437
|
-
info.push('Git hooks are configured correctly');
|
|
438
|
-
|
|
439
|
-
// Check hook files exist
|
|
440
|
-
const hookFiles = ['pre-commit', 'pre-push'];
|
|
441
|
-
hookFiles.forEach(hook => {
|
|
442
|
-
const hookFile = path.join(hooksPath, hook);
|
|
443
|
-
if (!fs.existsSync(hookFile)) {
|
|
444
|
-
warnings.push(`Missing hook file: ${hook}`);
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
} else {
|
|
448
|
-
warnings.push('Git hooks not pointing to Delimit. Run "delimit install" to fix.');
|
|
449
|
-
}
|
|
450
|
-
} catch (e) {
|
|
451
|
-
issues.push('Git hooks not configured. Run "delimit install" to set up.');
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Check PATH
|
|
455
|
-
const pathHasDelimit = process.env.PATH.includes('.delimit/shims');
|
|
456
|
-
if (pathHasDelimit) {
|
|
457
|
-
warnings.push('PATH hijacking is active (for AI tool interception)');
|
|
458
|
-
} else {
|
|
459
|
-
info.push('PATH is clean (no AI tool interception)');
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Check policy files
|
|
463
|
-
const policies = [];
|
|
464
|
-
if (fs.existsSync('delimit.yml')) {
|
|
465
|
-
policies.push('project');
|
|
466
|
-
// Validate policy
|
|
467
|
-
try {
|
|
468
|
-
const policy = yaml.load(fs.readFileSync('delimit.yml', 'utf8'));
|
|
469
|
-
if (!policy.rules) {
|
|
470
|
-
warnings.push('Project policy has no rules defined');
|
|
471
|
-
}
|
|
472
|
-
} catch (e) {
|
|
473
|
-
issues.push(`Project policy is invalid: ${e.message}`);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const userPolicyPath = path.join(process.env.HOME, '.config', 'delimit', 'delimit.yml');
|
|
478
|
-
if (fs.existsSync(userPolicyPath)) {
|
|
479
|
-
policies.push('user');
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (policies.length === 0) {
|
|
483
|
-
warnings.push('No policy files found. Run "delimit policy --init" to create one.');
|
|
484
|
-
} else {
|
|
485
|
-
info.push(`Policy files loaded: ${policies.join(', ')}`);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Check audit log
|
|
489
|
-
const auditDir = path.join(process.env.HOME, '.delimit', 'audit');
|
|
490
|
-
if (fs.existsSync(auditDir)) {
|
|
491
|
-
const files = fs.readdirSync(auditDir);
|
|
492
|
-
info.push(`Audit log has ${files.length} day(s) of history`);
|
|
493
|
-
} else {
|
|
494
|
-
warnings.push('No audit logs found yet');
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// Display results
|
|
498
|
-
if (issues.length > 0) {
|
|
499
|
-
console.log(chalk.red.bold('ā Issues Found:\n'));
|
|
500
|
-
issues.forEach(issue => console.log(chalk.red(` ⢠${issue}`)));
|
|
501
|
-
console.log();
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (warnings.length > 0) {
|
|
505
|
-
console.log(chalk.yellow.bold('ā ļø Warnings:\n'));
|
|
506
|
-
warnings.forEach(warning => console.log(chalk.yellow(` ⢠${warning}`)));
|
|
507
|
-
console.log();
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
if (info.length > 0) {
|
|
511
|
-
console.log(chalk.green.bold('ā
Working Correctly:\n'));
|
|
512
|
-
info.forEach(item => console.log(chalk.green(` ⢠${item}`)));
|
|
513
|
-
console.log();
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Overall status
|
|
517
|
-
if (issues.length === 0) {
|
|
518
|
-
console.log(chalk.green.bold('š Delimit is healthy!'));
|
|
519
|
-
} else {
|
|
520
|
-
console.log(chalk.red.bold('š§ Please fix the issues above'));
|
|
521
|
-
process.exit(1);
|
|
522
|
-
}
|
|
523
|
-
});
|
|
420
|
+
// Doctor command - verify setup for API governance
|
|
421
|
+
// (legacy doctor replaced with v1-focused checks)
|
|
524
422
|
|
|
525
423
|
// Explain-decision command - show governance decision reasoning
|
|
526
424
|
program
|
|
527
425
|
.command('explain-decision [decision-id]')
|
|
528
426
|
.description('Explain a governance decision')
|
|
427
|
+
|
|
529
428
|
.action(async (decisionId) => {
|
|
530
429
|
await ensureAgent();
|
|
531
430
|
|
|
@@ -545,6 +444,7 @@ program
|
|
|
545
444
|
program
|
|
546
445
|
.command('uninstall')
|
|
547
446
|
.description('Remove Delimit governance')
|
|
447
|
+
|
|
548
448
|
.action(async () => {
|
|
549
449
|
const { confirm } = await inquirer.prompt([{
|
|
550
450
|
type: 'confirm',
|
|
@@ -637,6 +537,7 @@ program
|
|
|
637
537
|
.command('proxy <tool>')
|
|
638
538
|
.allowUnknownOption()
|
|
639
539
|
.description('Proxy AI tool execution with governance')
|
|
540
|
+
|
|
640
541
|
.action(async (tool, options) => {
|
|
641
542
|
const { proxyAITool } = require('../lib/proxy-handler');
|
|
642
543
|
// Get all args after the tool name
|
|
@@ -649,6 +550,7 @@ program
|
|
|
649
550
|
program
|
|
650
551
|
.command('hook <type>')
|
|
651
552
|
.description('Internal hook handler')
|
|
553
|
+
|
|
652
554
|
.action(async (type) => {
|
|
653
555
|
await ensureAgent();
|
|
654
556
|
|
|
@@ -882,20 +784,167 @@ program
|
|
|
882
784
|
|
|
883
785
|
fs.mkdirSync(configDir, { recursive: true });
|
|
884
786
|
fs.writeFileSync(policyFile, POLICY_PRESETS[preset]);
|
|
885
|
-
console.log(chalk.green(
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
787
|
+
console.log(chalk.green(`\n Created .delimit/policies.yml (preset: ${preset})\n`));
|
|
788
|
+
|
|
789
|
+
// Auto-detect OpenAPI spec files
|
|
790
|
+
const specPatterns = [
|
|
791
|
+
'openapi.yaml', 'openapi.yml', 'openapi.json',
|
|
792
|
+
'swagger.yaml', 'swagger.yml', 'swagger.json',
|
|
793
|
+
'api.yaml', 'api.yml', 'api.json',
|
|
794
|
+
'docs/openapi.yaml', 'docs/openapi.yml', 'docs/openapi.json',
|
|
795
|
+
'spec/openapi.yaml', 'spec/openapi.json',
|
|
796
|
+
'specs/openapi.yaml', 'specs/openapi.json',
|
|
797
|
+
'api/openapi.yaml', 'api/openapi.json',
|
|
798
|
+
'contrib/openapi.json',
|
|
799
|
+
];
|
|
800
|
+
const foundSpecs = specPatterns.filter(p => fs.existsSync(path.join(process.cwd(), p)));
|
|
801
|
+
|
|
802
|
+
if (foundSpecs.length > 0) {
|
|
803
|
+
const specPath = foundSpecs[0];
|
|
804
|
+
console.log(` Detected spec: ${chalk.bold(specPath)}`);
|
|
805
|
+
console.log('');
|
|
806
|
+
console.log(chalk.bold(' Add this to .github/workflows/api-governance.yml:\n'));
|
|
807
|
+
console.log(chalk.gray(` name: API Governance
|
|
808
|
+
on:
|
|
809
|
+
pull_request:
|
|
810
|
+
paths:
|
|
811
|
+
- '${specPath}'
|
|
812
|
+
permissions:
|
|
813
|
+
contents: read
|
|
814
|
+
pull-requests: write
|
|
815
|
+
jobs:
|
|
816
|
+
api-governance:
|
|
817
|
+
runs-on: ubuntu-latest
|
|
818
|
+
steps:
|
|
819
|
+
- uses: actions/checkout@v4
|
|
820
|
+
- uses: actions/checkout@v4
|
|
821
|
+
with:
|
|
822
|
+
ref: \${{ github.event.pull_request.base.sha }}
|
|
823
|
+
path: _base
|
|
824
|
+
- uses: delimit-ai/delimit@v1
|
|
825
|
+
with:
|
|
826
|
+
old_spec: _base/${specPath}
|
|
827
|
+
new_spec: ${specPath}
|
|
828
|
+
mode: advisory`));
|
|
829
|
+
console.log('');
|
|
830
|
+
} else {
|
|
831
|
+
console.log(' No OpenAPI spec file detected.');
|
|
832
|
+
console.log(` Delimit also supports ${chalk.bold('Zero-Spec Mode')} ā run ${chalk.bold('delimit lint')} in a FastAPI/NestJS/Express project.`);
|
|
833
|
+
console.log('');
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
console.log(` ${chalk.bold('Presets')}: strict | default | relaxed`);
|
|
837
|
+
console.log(` Switch: ${chalk.bold('delimit init --preset strict')}\n`);
|
|
893
838
|
console.log('Next steps:');
|
|
894
839
|
console.log(` ${chalk.bold('delimit lint')} old.yaml new.yaml ā check for breaking changes`);
|
|
895
840
|
console.log(` ${chalk.bold('delimit diff')} old.yaml new.yaml ā see all changes`);
|
|
896
841
|
console.log(` ${chalk.bold('delimit explain')} old.yaml new.yaml ā human-readable summary`);
|
|
897
842
|
});
|
|
898
843
|
|
|
844
|
+
// Doctor command ā verify setup is correct
|
|
845
|
+
program
|
|
846
|
+
.command('doctor')
|
|
847
|
+
.description('Verify Delimit setup and diagnose common issues')
|
|
848
|
+
.action(async () => {
|
|
849
|
+
console.log(chalk.bold('\n Delimit Doctor\n'));
|
|
850
|
+
let ok = 0;
|
|
851
|
+
let warn = 0;
|
|
852
|
+
let fail = 0;
|
|
853
|
+
|
|
854
|
+
// Check policy file
|
|
855
|
+
const policyPath = path.join(process.cwd(), '.delimit', 'policies.yml');
|
|
856
|
+
if (fs.existsSync(policyPath)) {
|
|
857
|
+
console.log(chalk.green(' ā .delimit/policies.yml found'));
|
|
858
|
+
ok++;
|
|
859
|
+
try {
|
|
860
|
+
const yaml = require('js-yaml');
|
|
861
|
+
const policy = yaml.load(fs.readFileSync(policyPath, 'utf8'));
|
|
862
|
+
if (policy && (policy.rules !== undefined || policy.override_defaults !== undefined)) {
|
|
863
|
+
console.log(chalk.green(' ā Policy file is valid YAML'));
|
|
864
|
+
ok++;
|
|
865
|
+
} else {
|
|
866
|
+
console.log(chalk.yellow(' ā Policy file has no rules section'));
|
|
867
|
+
warn++;
|
|
868
|
+
}
|
|
869
|
+
} catch (e) {
|
|
870
|
+
console.log(chalk.red(` ā Policy file has invalid YAML: ${e.message}`));
|
|
871
|
+
fail++;
|
|
872
|
+
}
|
|
873
|
+
} else {
|
|
874
|
+
console.log(chalk.red(' ā No .delimit/policies.yml ā run: delimit init'));
|
|
875
|
+
fail++;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Check for OpenAPI spec
|
|
879
|
+
const specPatterns = [
|
|
880
|
+
'openapi.yaml', 'openapi.yml', 'openapi.json',
|
|
881
|
+
'swagger.yaml', 'swagger.yml', 'swagger.json',
|
|
882
|
+
'docs/openapi.yaml', 'docs/openapi.yml', 'docs/openapi.json',
|
|
883
|
+
'spec/openapi.yaml', 'spec/openapi.json',
|
|
884
|
+
'api/openapi.yaml', 'api/openapi.json',
|
|
885
|
+
'contrib/openapi.json',
|
|
886
|
+
];
|
|
887
|
+
const foundSpecs = specPatterns.filter(p => fs.existsSync(path.join(process.cwd(), p)));
|
|
888
|
+
if (foundSpecs.length > 0) {
|
|
889
|
+
console.log(chalk.green(` ā OpenAPI spec found: ${foundSpecs[0]}`));
|
|
890
|
+
ok++;
|
|
891
|
+
} else {
|
|
892
|
+
// Check for framework (Zero-Spec candidate)
|
|
893
|
+
const pkgJson = path.join(process.cwd(), 'package.json');
|
|
894
|
+
const reqTxt = path.join(process.cwd(), 'requirements.txt');
|
|
895
|
+
if (fs.existsSync(pkgJson) || fs.existsSync(reqTxt)) {
|
|
896
|
+
console.log(chalk.yellow(' ā No OpenAPI spec file ā Zero-Spec Mode may work if this is a FastAPI/NestJS/Express project'));
|
|
897
|
+
warn++;
|
|
898
|
+
} else {
|
|
899
|
+
console.log(chalk.red(' ā No OpenAPI spec file found'));
|
|
900
|
+
fail++;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Check for GitHub workflow
|
|
905
|
+
const workflowDir = path.join(process.cwd(), '.github', 'workflows');
|
|
906
|
+
if (fs.existsSync(workflowDir)) {
|
|
907
|
+
const workflows = fs.readdirSync(workflowDir);
|
|
908
|
+
const hasDelimit = workflows.some(f => {
|
|
909
|
+
try {
|
|
910
|
+
const content = fs.readFileSync(path.join(workflowDir, f), 'utf8');
|
|
911
|
+
return content.includes('delimit-ai/delimit') || content.includes('delimit');
|
|
912
|
+
} catch { return false; }
|
|
913
|
+
});
|
|
914
|
+
if (hasDelimit) {
|
|
915
|
+
console.log(chalk.green(' ā GitHub Action workflow found'));
|
|
916
|
+
ok++;
|
|
917
|
+
} else {
|
|
918
|
+
console.log(chalk.yellow(' ā No Delimit GitHub Action workflow ā run delimit init for setup instructions'));
|
|
919
|
+
warn++;
|
|
920
|
+
}
|
|
921
|
+
} else {
|
|
922
|
+
console.log(chalk.yellow(' ā No .github/workflows/ directory'));
|
|
923
|
+
warn++;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Check git
|
|
927
|
+
try {
|
|
928
|
+
const { execSync } = require('child_process');
|
|
929
|
+
execSync('git rev-parse --git-dir', { stdio: 'pipe' });
|
|
930
|
+
console.log(chalk.green(' ā Git repository detected'));
|
|
931
|
+
ok++;
|
|
932
|
+
} catch {
|
|
933
|
+
console.log(chalk.yellow(' ā Not a git repository'));
|
|
934
|
+
warn++;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Summary
|
|
938
|
+
console.log('');
|
|
939
|
+
if (fail === 0 && warn === 0) {
|
|
940
|
+
console.log(chalk.green.bold(' All checks passed! Ready to lint.\n'));
|
|
941
|
+
} else if (fail === 0) {
|
|
942
|
+
console.log(chalk.yellow.bold(` ${ok} passed, ${warn} warning(s). Setup looks good.\n`));
|
|
943
|
+
} else {
|
|
944
|
+
console.log(chalk.red.bold(` ${ok} passed, ${warn} warning(s), ${fail} error(s). Fix errors above.\n`));
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
|
|
899
948
|
// Lint command ā diff + policy (primary command)
|
|
900
949
|
// Supports zero-spec mode: `delimit lint` (no args) auto-extracts from FastAPI
|
|
901
950
|
program
|
|
@@ -1078,4 +1127,11 @@ program
|
|
|
1078
1127
|
}
|
|
1079
1128
|
});
|
|
1080
1129
|
|
|
1130
|
+
// Hide legacy/internal commands from --help
|
|
1131
|
+
['install', 'mode', 'status', 'policy', 'auth', 'audit',
|
|
1132
|
+
'explain-decision', 'uninstall', 'proxy', 'hook'].forEach(name => {
|
|
1133
|
+
const cmd = program.commands.find(c => c.name() === name);
|
|
1134
|
+
if (cmd) cmd._hidden = true;
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1081
1137
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "delimit-cli",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Prevent breaking API changes before they reach production. Deterministic diff engine + policy enforcement + semver classification.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"postinstall": "echo 'Run \"delimit install\" to set up governance'",
|
|
11
11
|
"install": "bash ./hooks/install-hooks.sh install",
|
|
12
|
-
"install-mcp": "bash ./hooks/install-hooks.sh mcp-only",
|
|
12
|
+
"install-mcp": "bash ./hooks/install-hooks.sh mcp-only",
|
|
13
13
|
"test-mcp": "bash ./hooks/install-hooks.sh troubleshoot",
|
|
14
14
|
"fix-mcp": "bash ./hooks/install-hooks.sh fix-mcp",
|
|
15
15
|
"test": "echo 'Governance is context-aware' && exit 0"
|
|
@@ -44,15 +44,15 @@
|
|
|
44
44
|
"url": "https://github.com/delimit-ai/delimit.git"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"commander": "^9.0.0",
|
|
48
47
|
"axios": "^1.0.0",
|
|
49
48
|
"chalk": "^4.1.2",
|
|
50
|
-
"
|
|
49
|
+
"commander": "^12.1.0",
|
|
51
50
|
"express": "^4.18.0",
|
|
51
|
+
"inquirer": "^8.2.0",
|
|
52
52
|
"js-yaml": "^4.1.0",
|
|
53
53
|
"minimatch": "^5.1.0"
|
|
54
54
|
},
|
|
55
55
|
"engines": {
|
|
56
56
|
"node": ">=14.0.0"
|
|
57
57
|
}
|
|
58
|
-
}
|
|
58
|
+
}
|