delimit-cli 4.1.26 → 4.1.28
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 +2 -1
- package/bin/delimit-cli.js +177 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Works across any configuration — from a single model on a budget to an enterpr
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
npx delimit-cli scan # Instant health grade for your API spec
|
|
34
|
-
npx delimit-cli
|
|
34
|
+
npx delimit-cli pr owner/repo#123 # Review any GitHub PR for breaking changes
|
|
35
35
|
npx delimit-cli setup && source ~/.bashrc # Configure AI assistants + activate
|
|
36
36
|
```
|
|
37
37
|
|
|
@@ -143,6 +143,7 @@ That's it. Delimit auto-fetches the base branch spec, diffs it, and posts a PR c
|
|
|
143
143
|
|
|
144
144
|
```bash
|
|
145
145
|
npx delimit-cli scan # Instant spec health grade + recommendations
|
|
146
|
+
npx delimit-cli pr owner/repo#123 # Review any GitHub PR for breaking changes
|
|
146
147
|
npx delimit-cli quickstart # Clone demo project + guided walkthrough
|
|
147
148
|
npx delimit-cli try # Zero-risk demo — saves governance report
|
|
148
149
|
npx delimit-cli demo # Self-contained governance demo
|
package/bin/delimit-cli.js
CHANGED
|
@@ -2560,6 +2560,183 @@ program
|
|
|
2560
2560
|
}
|
|
2561
2561
|
});
|
|
2562
2562
|
|
|
2563
|
+
// PR review command — review any GitHub PR for breaking API changes
|
|
2564
|
+
program
|
|
2565
|
+
.command('pr <url>')
|
|
2566
|
+
.description('Review a GitHub PR for breaking API changes')
|
|
2567
|
+
.action(async (url) => {
|
|
2568
|
+
console.log(chalk.bold('\n Delimit PR Review\n'));
|
|
2569
|
+
|
|
2570
|
+
// Parse GitHub PR URL: owner/repo#number or full URL
|
|
2571
|
+
let owner, repo, prNumber;
|
|
2572
|
+
const urlMatch = url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
2573
|
+
const shortMatch = url.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
2574
|
+
if (urlMatch) {
|
|
2575
|
+
[, owner, repo, prNumber] = urlMatch;
|
|
2576
|
+
} else if (shortMatch) {
|
|
2577
|
+
[, owner, repo, prNumber] = shortMatch;
|
|
2578
|
+
} else if (/^\d+$/.test(url)) {
|
|
2579
|
+
// Just a number — try current repo
|
|
2580
|
+
try {
|
|
2581
|
+
const remote = execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
2582
|
+
const remoteMatch = remote.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
2583
|
+
if (remoteMatch) {
|
|
2584
|
+
[, owner, repo] = remoteMatch;
|
|
2585
|
+
prNumber = url;
|
|
2586
|
+
}
|
|
2587
|
+
} catch {}
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
if (!owner || !repo || !prNumber) {
|
|
2591
|
+
console.log(chalk.red(' Could not parse PR URL.'));
|
|
2592
|
+
console.log(chalk.gray(' Usage: npx delimit-cli pr owner/repo#123'));
|
|
2593
|
+
console.log(chalk.gray(' npx delimit-cli pr https://github.com/owner/repo/pull/123'));
|
|
2594
|
+
console.log(chalk.gray(' npx delimit-cli pr 123 (in a git repo)\n'));
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
console.log(chalk.gray(` Reviewing ${owner}/${repo}#${prNumber}...\n`));
|
|
2599
|
+
|
|
2600
|
+
// Get PR changed files
|
|
2601
|
+
try {
|
|
2602
|
+
const filesJson = execSync(
|
|
2603
|
+
`gh api repos/${owner}/${repo}/pulls/${prNumber}/files --paginate 2>/dev/null`,
|
|
2604
|
+
{ encoding: 'utf-8', timeout: 15000 }
|
|
2605
|
+
);
|
|
2606
|
+
const files = JSON.parse(filesJson);
|
|
2607
|
+
const specPatterns = ['openapi', 'swagger', 'api-spec', 'api_spec'];
|
|
2608
|
+
const specExts = ['.yaml', '.yml', '.json'];
|
|
2609
|
+
const specFiles = files.filter(f => {
|
|
2610
|
+
const name = f.filename.toLowerCase();
|
|
2611
|
+
return specExts.some(ext => name.endsWith(ext)) &&
|
|
2612
|
+
specPatterns.some(p => name.includes(p));
|
|
2613
|
+
});
|
|
2614
|
+
|
|
2615
|
+
if (specFiles.length === 0) {
|
|
2616
|
+
console.log(chalk.gray(' No OpenAPI/Swagger spec changes found in this PR.'));
|
|
2617
|
+
console.log(chalk.gray(' Delimit reviews PRs that modify API spec files.\n'));
|
|
2618
|
+
// Show what files were changed
|
|
2619
|
+
const apiFiles = files.filter(f => f.filename.includes('api') || f.filename.includes('spec'));
|
|
2620
|
+
if (apiFiles.length > 0) {
|
|
2621
|
+
console.log(chalk.gray(' API-related files changed:'));
|
|
2622
|
+
apiFiles.slice(0, 5).forEach(f => console.log(chalk.gray(` ${f.filename} (+${f.additions}/-${f.deletions})`)));
|
|
2623
|
+
console.log('');
|
|
2624
|
+
}
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
console.log(` ${chalk.green('✓')} Found ${specFiles.length} spec file(s) changed:\n`);
|
|
2629
|
+
specFiles.forEach(f => {
|
|
2630
|
+
console.log(` ${chalk.cyan(f.filename)} (+${f.additions}/-${f.deletions})`);
|
|
2631
|
+
});
|
|
2632
|
+
console.log('');
|
|
2633
|
+
|
|
2634
|
+
// Fetch base and head versions of the first spec
|
|
2635
|
+
const specFile = specFiles[0];
|
|
2636
|
+
const tmpDir = path.join(os.tmpdir(), `delimit-pr-${Date.now()}`);
|
|
2637
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
2638
|
+
|
|
2639
|
+
// Get PR details for base/head refs
|
|
2640
|
+
const prJson = execSync(
|
|
2641
|
+
`gh api repos/${owner}/${repo}/pulls/${prNumber} 2>/dev/null`,
|
|
2642
|
+
{ encoding: 'utf-8', timeout: 10000 }
|
|
2643
|
+
);
|
|
2644
|
+
const pr = JSON.parse(prJson);
|
|
2645
|
+
const baseSha = pr.base.sha;
|
|
2646
|
+
const headSha = pr.head.sha;
|
|
2647
|
+
|
|
2648
|
+
// Fetch both versions
|
|
2649
|
+
const basePath = path.join(tmpDir, 'base.yaml');
|
|
2650
|
+
const headPath = path.join(tmpDir, 'head.yaml');
|
|
2651
|
+
|
|
2652
|
+
try {
|
|
2653
|
+
const baseContent = execSync(
|
|
2654
|
+
`gh api repos/${owner}/${repo}/contents/${specFile.filename}?ref=${baseSha} -q '.content' 2>/dev/null`,
|
|
2655
|
+
{ encoding: 'utf-8', timeout: 10000 }
|
|
2656
|
+
);
|
|
2657
|
+
fs.writeFileSync(basePath, Buffer.from(baseContent.trim(), 'base64').toString());
|
|
2658
|
+
} catch {
|
|
2659
|
+
console.log(chalk.yellow(' New spec file (no base version). Cannot diff.\n'));
|
|
2660
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
try {
|
|
2665
|
+
const headContent = execSync(
|
|
2666
|
+
`gh api repos/${owner}/${repo}/contents/${specFile.filename}?ref=${headSha} -q '.content' 2>/dev/null`,
|
|
2667
|
+
{ encoding: 'utf-8', timeout: 10000 }
|
|
2668
|
+
);
|
|
2669
|
+
fs.writeFileSync(headPath, Buffer.from(headContent.trim(), 'base64').toString());
|
|
2670
|
+
} catch {
|
|
2671
|
+
console.log(chalk.red(' Could not fetch head version of spec.\n'));
|
|
2672
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
// Run lint
|
|
2677
|
+
console.log(chalk.gray(' Running governance pipeline...\n'));
|
|
2678
|
+
const bundledGateway = path.join(__dirname, '..', 'gateway');
|
|
2679
|
+
const serverDir = (continuityContext.serverDir && fs.existsSync(continuityContext.serverDir))
|
|
2680
|
+
? continuityContext.serverDir
|
|
2681
|
+
: fs.existsSync(bundledGateway) ? bundledGateway : null;
|
|
2682
|
+
|
|
2683
|
+
if (!serverDir) {
|
|
2684
|
+
console.log(chalk.yellow(' Gateway not available. Run: npx delimit-cli setup\n'));
|
|
2685
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
try {
|
|
2690
|
+
const result = execSync(
|
|
2691
|
+
`python3 -c "
|
|
2692
|
+
import sys,json,yaml
|
|
2693
|
+
sys.path.insert(0,'${serverDir}')
|
|
2694
|
+
from core.diff_engine_v2 import OpenAPIDiffEngine
|
|
2695
|
+
old=yaml.safe_load(open('${basePath}'))
|
|
2696
|
+
new=yaml.safe_load(open('${headPath}'))
|
|
2697
|
+
engine=OpenAPIDiffEngine()
|
|
2698
|
+
changes=engine.compare(old,new)
|
|
2699
|
+
print(json.dumps({'changes':[{'type':c.type.value if hasattr(c.type,'value') else str(c.type),'path':c.path,'breaking':c.severity in ('high','critical','error'),'detail':c.message or c.details or ''} for c in changes]}))
|
|
2700
|
+
"`,
|
|
2701
|
+
{ encoding: 'utf-8', timeout: 15000, cwd: serverDir }
|
|
2702
|
+
);
|
|
2703
|
+
const diff = JSON.parse(result);
|
|
2704
|
+
const breaking = (diff.changes || []).filter(c => c.breaking);
|
|
2705
|
+
const nonBreaking = (diff.changes || []).filter(c => !c.breaking);
|
|
2706
|
+
|
|
2707
|
+
if (breaking.length > 0) {
|
|
2708
|
+
console.log(chalk.red.bold(` ${breaking.length} BREAKING change(s) detected\n`));
|
|
2709
|
+
breaking.forEach(c => {
|
|
2710
|
+
console.log(` ${chalk.red('BREAK')} ${c.type}: ${c.path || ''}`);
|
|
2711
|
+
if (c.detail) console.log(chalk.gray(` ${c.detail}`));
|
|
2712
|
+
});
|
|
2713
|
+
} else {
|
|
2714
|
+
console.log(chalk.green.bold(' No breaking changes detected'));
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
if (nonBreaking.length > 0) {
|
|
2718
|
+
console.log(chalk.gray(`\n ${nonBreaking.length} non-breaking change(s)`));
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
// Semver classification
|
|
2722
|
+
const bump = breaking.length > 0 ? 'MAJOR' : nonBreaking.length > 0 ? 'MINOR' : 'NONE';
|
|
2723
|
+
console.log(`\n Semver: ${chalk.bold(bump)}`);
|
|
2724
|
+
console.log(` Total: ${(diff.changes || []).length} changes (${breaking.length} breaking, ${nonBreaking.length} compatible)\n`);
|
|
2725
|
+
|
|
2726
|
+
console.log(chalk.bold(' Add to your repo:'));
|
|
2727
|
+
console.log(chalk.green(` npx delimit-cli init`));
|
|
2728
|
+
console.log(chalk.gray(' Catches this on every PR automatically.\n'));
|
|
2729
|
+
} catch (e) {
|
|
2730
|
+
console.log(chalk.red(` Diff failed: ${e.message.split('\n')[0]}\n`));
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2734
|
+
} catch (e) {
|
|
2735
|
+
console.log(chalk.red(` Error: ${e.message.split('\n')[0]}`));
|
|
2736
|
+
console.log(chalk.gray(' Make sure gh CLI is authenticated: gh auth login\n'));
|
|
2737
|
+
}
|
|
2738
|
+
});
|
|
2739
|
+
|
|
2563
2740
|
// Try command — zero-risk demo with Markdown report artifact (LED-264)
|
|
2564
2741
|
program
|
|
2565
2742
|
.command('try')
|
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.
|
|
4
|
+
"version": "4.1.28",
|
|
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": [
|