delimit-cli 4.1.25 → 4.1.27
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 +213 -3
- package/gateway/ai/server.py +140 -10
- package/package.json +1 -1
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')
|
|
@@ -3133,23 +3310,56 @@ program
|
|
|
3133
3310
|
process.exit(1);
|
|
3134
3311
|
}
|
|
3135
3312
|
|
|
3313
|
+
console.log(chalk.gray(' Validating license key...'));
|
|
3314
|
+
|
|
3315
|
+
// Validate against Lemon Squeezy API
|
|
3316
|
+
let validated = false;
|
|
3317
|
+
let licenseId = null;
|
|
3318
|
+
let customerEmail = '';
|
|
3319
|
+
try {
|
|
3320
|
+
const resp = await axios.post('https://api.lemonsqueezy.com/v1/licenses/validate', {
|
|
3321
|
+
license_key: key,
|
|
3322
|
+
}, {
|
|
3323
|
+
headers: { 'Accept': 'application/json' },
|
|
3324
|
+
timeout: 10000,
|
|
3325
|
+
});
|
|
3326
|
+
if (resp.data && resp.data.valid) {
|
|
3327
|
+
validated = true;
|
|
3328
|
+
licenseId = resp.data.license_key?.id;
|
|
3329
|
+
customerEmail = resp.data.meta?.customer_email || '';
|
|
3330
|
+
console.log(chalk.green(' License valid.'));
|
|
3331
|
+
} else {
|
|
3332
|
+
console.log(chalk.red(` License invalid: ${resp.data?.error || 'unknown error'}`));
|
|
3333
|
+
process.exit(1);
|
|
3334
|
+
}
|
|
3335
|
+
} catch (err) {
|
|
3336
|
+
// If API unreachable, accept locally (grace period)
|
|
3337
|
+
console.log(chalk.yellow(' Could not reach license server. Activating locally (7-day grace).'));
|
|
3338
|
+
validated = true;
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3136
3341
|
// Write license file
|
|
3137
3342
|
const crypto = require('crypto');
|
|
3138
3343
|
const machineHash = crypto.createHash('sha256').update(os.homedir()).digest('hex').slice(0, 16);
|
|
3139
3344
|
const licenseData = {
|
|
3140
3345
|
key: key,
|
|
3141
3346
|
tier: 'pro',
|
|
3142
|
-
valid:
|
|
3347
|
+
valid: validated,
|
|
3348
|
+
license_id: licenseId,
|
|
3349
|
+
customer_email: customerEmail,
|
|
3143
3350
|
activated_at: Date.now() / 1000,
|
|
3144
3351
|
machine_hash: machineHash,
|
|
3352
|
+
validated_at: Date.now() / 1000,
|
|
3145
3353
|
};
|
|
3146
3354
|
|
|
3147
3355
|
if (!fs.existsSync(licenseDir)) {
|
|
3148
3356
|
fs.mkdirSync(licenseDir, { recursive: true });
|
|
3149
3357
|
}
|
|
3150
3358
|
fs.writeFileSync(licensePath, JSON.stringify(licenseData, null, 2));
|
|
3151
|
-
console.log(chalk.green('License activated successfully.'));
|
|
3152
|
-
console.log(chalk.dim(
|
|
3359
|
+
console.log(chalk.green('\n License activated successfully.'));
|
|
3360
|
+
console.log(chalk.dim(` Tier: pro`));
|
|
3361
|
+
if (customerEmail) console.log(chalk.dim(` Email: ${customerEmail}`));
|
|
3362
|
+
console.log('');
|
|
3153
3363
|
});
|
|
3154
3364
|
|
|
3155
3365
|
// ---------------------------------------------------------------------------
|
package/gateway/ai/server.py
CHANGED
|
@@ -5756,16 +5756,108 @@ def delimit_quickstart(project_path: str = ".") -> Dict[str, Any]:
|
|
|
5756
5756
|
"models": enabled_models,
|
|
5757
5757
|
})
|
|
5758
5758
|
|
|
5759
|
+
# Step 6: First Governance Run -- show value with bundled example specs
|
|
5760
|
+
demo_result: Dict[str, Any] = {"skipped": False}
|
|
5761
|
+
examples_dir = Path(__file__).resolve().parent.parent / "examples"
|
|
5762
|
+
petstore_v1 = examples_dir / "petstore-v1.yaml"
|
|
5763
|
+
petstore_v2 = examples_dir / "petstore-v2.yaml"
|
|
5764
|
+
if petstore_v1.is_file() and petstore_v2.is_file():
|
|
5765
|
+
from backends.gateway_core import run_lint as _qs_run_lint, run_spec_health as _qs_run_spec_health
|
|
5766
|
+
|
|
5767
|
+
# 6a: Lint petstore v1 vs v2 to show breaking change detection
|
|
5768
|
+
try:
|
|
5769
|
+
lint_demo = _qs_run_lint(
|
|
5770
|
+
old_spec=str(petstore_v1),
|
|
5771
|
+
new_spec=str(petstore_v2),
|
|
5772
|
+
)
|
|
5773
|
+
breaking_count = len(lint_demo.get("breaking", lint_demo.get("violations", [])))
|
|
5774
|
+
total_changes = lint_demo.get("total_changes", 0)
|
|
5775
|
+
demo_result["lint"] = {
|
|
5776
|
+
"breaking_changes": breaking_count,
|
|
5777
|
+
"total_changes": total_changes,
|
|
5778
|
+
"status": lint_demo.get("status", "unknown"),
|
|
5779
|
+
"sample_violations": [
|
|
5780
|
+
v.get("message", v.get("type", "unknown"))
|
|
5781
|
+
for v in lint_demo.get("breaking", lint_demo.get("violations", []))[:3]
|
|
5782
|
+
],
|
|
5783
|
+
}
|
|
5784
|
+
except Exception as e:
|
|
5785
|
+
demo_result["lint"] = {"error": str(e)}
|
|
5786
|
+
|
|
5787
|
+
# 6b: Spec health score on petstore v1
|
|
5788
|
+
try:
|
|
5789
|
+
health_demo = _qs_run_spec_health(spec_path=str(petstore_v1))
|
|
5790
|
+
demo_result["spec_health"] = {
|
|
5791
|
+
"score": health_demo.get("score", health_demo.get("overall_score")),
|
|
5792
|
+
"grade": health_demo.get("grade", health_demo.get("letter_grade")),
|
|
5793
|
+
"dimensions": {
|
|
5794
|
+
k: v for k, v in health_demo.get("dimensions", {}).items()
|
|
5795
|
+
} if health_demo.get("dimensions") else {},
|
|
5796
|
+
"recommendations_count": len(health_demo.get("recommendations", [])),
|
|
5797
|
+
}
|
|
5798
|
+
except Exception as e:
|
|
5799
|
+
demo_result["spec_health"] = {"error": str(e)}
|
|
5800
|
+
else:
|
|
5801
|
+
demo_result["skipped"] = True
|
|
5802
|
+
demo_result["reason"] = "Example specs not found"
|
|
5803
|
+
|
|
5804
|
+
steps_completed.append({
|
|
5805
|
+
"step": 6,
|
|
5806
|
+
"name": "First Governance Run (Demo)",
|
|
5807
|
+
"result": demo_result,
|
|
5808
|
+
})
|
|
5809
|
+
|
|
5810
|
+
# Step 7: Project Spec Discovery -- check if this project has OpenAPI specs
|
|
5811
|
+
project_specs: List[str] = []
|
|
5812
|
+
project_lint_result: Optional[Dict[str, Any]] = None
|
|
5813
|
+
spec_patterns = [
|
|
5814
|
+
"**/openapi.yaml", "**/openapi.yml", "**/openapi.json",
|
|
5815
|
+
"**/swagger.yaml", "**/swagger.yml", "**/swagger.json",
|
|
5816
|
+
]
|
|
5817
|
+
for pattern in spec_patterns:
|
|
5818
|
+
for match in p.glob(pattern):
|
|
5819
|
+
rel = str(match.relative_to(p))
|
|
5820
|
+
if "node_modules" not in rel and ".next" not in rel and "venv" not in rel:
|
|
5821
|
+
project_specs.append(str(match))
|
|
5822
|
+
project_specs = list(set(project_specs))[:5]
|
|
5823
|
+
|
|
5824
|
+
if project_specs:
|
|
5825
|
+
# Run spec_health on the first discovered spec
|
|
5826
|
+
try:
|
|
5827
|
+
from backends.gateway_core import run_spec_health as _qs_health
|
|
5828
|
+
proj_health = _qs_health(spec_path=project_specs[0])
|
|
5829
|
+
project_lint_result = {
|
|
5830
|
+
"spec": project_specs[0],
|
|
5831
|
+
"score": proj_health.get("score", proj_health.get("overall_score")),
|
|
5832
|
+
"grade": proj_health.get("grade", proj_health.get("letter_grade")),
|
|
5833
|
+
}
|
|
5834
|
+
except Exception as e:
|
|
5835
|
+
project_lint_result = {"spec": project_specs[0], "error": str(e)}
|
|
5836
|
+
|
|
5837
|
+
steps_completed.append({
|
|
5838
|
+
"step": 7,
|
|
5839
|
+
"name": "Project Spec Discovery",
|
|
5840
|
+
"specs_found": len(project_specs),
|
|
5841
|
+
"spec_files": project_specs,
|
|
5842
|
+
"health_result": project_lint_result,
|
|
5843
|
+
})
|
|
5844
|
+
|
|
5759
5845
|
# Build suggested next actions based on findings
|
|
5760
5846
|
next_actions = []
|
|
5761
|
-
if
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5847
|
+
if project_specs:
|
|
5848
|
+
next_actions.append(f"Run `delimit_spec_health` on {project_specs[0]} to see your full quality report")
|
|
5849
|
+
if len(project_specs) > 1:
|
|
5850
|
+
next_actions.append(f"You have {len(project_specs)} OpenAPI specs -- run `delimit_lint` to compare versions")
|
|
5851
|
+
next_actions.append("Add the Delimit GitHub Action to catch breaking changes on every PR")
|
|
5852
|
+
else:
|
|
5853
|
+
if scan_result.get("findings"):
|
|
5854
|
+
for f in scan_result["findings"]:
|
|
5855
|
+
if f.get("type") == "openapi_specs":
|
|
5856
|
+
next_actions.append("Run `delimit_lint` on your OpenAPI spec to check for breaking changes")
|
|
5857
|
+
if f.get("type") == "security_concerns":
|
|
5858
|
+
next_actions.append("Run `delimit_security_scan` to audit for vulnerabilities")
|
|
5859
|
+
if f.get("type") == "tests_found":
|
|
5860
|
+
next_actions.append("Run `delimit_test_smoke` to verify tests pass")
|
|
5769
5861
|
|
|
5770
5862
|
if not deliberation_ready:
|
|
5771
5863
|
next_actions.append("Add more AI models for multi-model deliberation: say 'configure delimit models'")
|
|
@@ -5773,16 +5865,54 @@ def delimit_quickstart(project_path: str = ".") -> Dict[str, Any]:
|
|
|
5773
5865
|
next_actions.append("Say 'add to ledger: [task]' to start tracking work across sessions")
|
|
5774
5866
|
next_actions.append("Say 'deliberate [question]' to get AI consensus on a decision")
|
|
5775
5867
|
|
|
5868
|
+
# Build the "wow moment" summary
|
|
5869
|
+
wow_moment: Dict[str, Any] = {}
|
|
5870
|
+
lint_data = demo_result.get("lint", {})
|
|
5871
|
+
health_data = demo_result.get("spec_health", {})
|
|
5872
|
+
if lint_data and not lint_data.get("error"):
|
|
5873
|
+
wow_moment["breaking_changes_caught"] = lint_data.get("breaking_changes", 0)
|
|
5874
|
+
wow_moment["total_api_changes"] = lint_data.get("total_changes", 0)
|
|
5875
|
+
wow_moment["sample_catches"] = lint_data.get("sample_violations", [])
|
|
5876
|
+
if health_data and not health_data.get("error"):
|
|
5877
|
+
wow_moment["spec_health_grade"] = health_data.get("grade")
|
|
5878
|
+
wow_moment["spec_health_score"] = health_data.get("score")
|
|
5879
|
+
wow_moment["governance_gates"] = [
|
|
5880
|
+
"Breaking change detection (CI/CD)",
|
|
5881
|
+
"Spec health scoring (quality)",
|
|
5882
|
+
"Policy enforcement (custom rules)",
|
|
5883
|
+
"Semver classification (automated)",
|
|
5884
|
+
"Contract ledger (audit trail)",
|
|
5885
|
+
]
|
|
5886
|
+
if project_specs:
|
|
5887
|
+
wow_moment["your_project"] = {
|
|
5888
|
+
"specs_found": len(project_specs),
|
|
5889
|
+
"ready_to_govern": True,
|
|
5890
|
+
}
|
|
5891
|
+
if project_lint_result and not project_lint_result.get("error"):
|
|
5892
|
+
wow_moment["your_project"]["health_grade"] = project_lint_result.get("grade")
|
|
5893
|
+
wow_moment["your_project"]["health_score"] = project_lint_result.get("score")
|
|
5894
|
+
|
|
5895
|
+
bc = wow_moment.get("breaking_changes_caught", 0)
|
|
5896
|
+
grade = wow_moment.get("spec_health_grade", "N/A")
|
|
5897
|
+
msg_parts = [
|
|
5898
|
+
f"Quickstart complete! {len(steps_completed)} steps run.",
|
|
5899
|
+
f"Demo: {bc} breaking changes caught, spec health grade: {grade}.",
|
|
5900
|
+
f"5 governance gates ready.",
|
|
5901
|
+
]
|
|
5902
|
+
if project_specs:
|
|
5903
|
+
msg_parts.append(f"Found {len(project_specs)} OpenAPI spec(s) in your project -- ready to govern.")
|
|
5904
|
+
|
|
5776
5905
|
return _with_next_steps("quickstart", {
|
|
5777
5906
|
"tool": "quickstart",
|
|
5778
5907
|
"status": "complete",
|
|
5779
5908
|
"project": str(p),
|
|
5780
5909
|
"steps": steps_completed,
|
|
5910
|
+
"wow_moment": wow_moment,
|
|
5781
5911
|
"environment": environment,
|
|
5782
5912
|
"scan_findings": scan_result.get("findings", []),
|
|
5783
5913
|
"scan_suggestions": scan_result.get("suggestions", []),
|
|
5784
5914
|
"next_actions": next_actions,
|
|
5785
|
-
"message":
|
|
5915
|
+
"message": " ".join(msg_parts),
|
|
5786
5916
|
})
|
|
5787
5917
|
|
|
5788
5918
|
|
|
@@ -6817,7 +6947,7 @@ def delimit_build_loop(action: str = "run", session_id: str = "") -> Dict[str, A
|
|
|
6817
6947
|
"""Execute the governed continuous build loop (LED-239).
|
|
6818
6948
|
|
|
6819
6949
|
Requirements:
|
|
6820
|
-
- root ledger in
|
|
6950
|
+
- root ledger in /root/.delimit is authoritative
|
|
6821
6951
|
- select only build-safe open items
|
|
6822
6952
|
- resolve venture + repo before dispatch
|
|
6823
6953
|
- use Delimit swarm/governance as control plane
|
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.27",
|
|
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": [
|