delimit-cli 3.7.1 → 3.8.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/README.md +1 -1
- package/bin/delimit-setup.js +30 -5
- package/gateway/ai/server.py +134 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ That's it. Delimit auto-fetches the base branch spec, diffs it, and posts a PR c
|
|
|
39
39
|
- Step-by-step migration guide
|
|
40
40
|
- Policy violations
|
|
41
41
|
|
|
42
|
-
[View on GitHub Marketplace →](https://github.com/marketplace/actions/delimit-api-governance) · [See a live
|
|
42
|
+
[View on GitHub Marketplace →](https://github.com/marketplace/actions/delimit-api-governance) · [See a live demo (6 breaking changes) →](https://github.com/delimit-ai/delimit-action-demo/pull/1)
|
|
43
43
|
|
|
44
44
|
### Example PR comment
|
|
45
45
|
|
package/bin/delimit-setup.js
CHANGED
|
@@ -327,6 +327,25 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
+
// Create instruction files for other platforms
|
|
331
|
+
const instructionContent = getClaudeMdContent();
|
|
332
|
+
|
|
333
|
+
// Codex instructions
|
|
334
|
+
const codexInstructions = path.join(os.homedir(), '.codex', 'instructions.md');
|
|
335
|
+
if (fs.existsSync(path.join(os.homedir(), '.codex'))) {
|
|
336
|
+
if (!fs.existsSync(codexInstructions) || fs.readFileSync(codexInstructions, 'utf-8').includes('# Delimit')) {
|
|
337
|
+
fs.writeFileSync(codexInstructions, instructionContent);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Cursor rules
|
|
342
|
+
const cursorRules = path.join(os.homedir(), '.cursorrules');
|
|
343
|
+
if (fs.existsSync(path.join(os.homedir(), '.cursor'))) {
|
|
344
|
+
if (!fs.existsSync(cursorRules) || fs.readFileSync(cursorRules, 'utf-8').includes('# Delimit')) {
|
|
345
|
+
fs.writeFileSync(cursorRules, instructionContent);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
330
349
|
// Silent: auto-detect API keys and configure models.json (no output)
|
|
331
350
|
const modelsPath = path.join(DELIMIT_HOME, 'models.json');
|
|
332
351
|
if (!fs.existsSync(modelsPath)) {
|
|
@@ -365,12 +384,12 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
365
384
|
log(' Try it now:');
|
|
366
385
|
log(` ${bold('$ claude')}`);
|
|
367
386
|
log('');
|
|
368
|
-
log(` Then say: ${blue('"
|
|
387
|
+
log(` Then say: ${blue('"scan this project"')}`);
|
|
369
388
|
log('');
|
|
370
389
|
log(' Or try:');
|
|
371
|
-
log(` ${dim('-')} "
|
|
372
|
-
log(` ${dim('-')} "
|
|
373
|
-
log(` ${dim('-')} "
|
|
390
|
+
log(` ${dim('-')} "lint my API spec" ${dim('— catch breaking changes')}`);
|
|
391
|
+
log(` ${dim('-')} "add to ledger: set up CI pipeline" ${dim('— track tasks across sessions')}`);
|
|
392
|
+
log(` ${dim('-')} "deliberate [question]" ${dim('— multi-model AI consensus')}`);
|
|
374
393
|
log('');
|
|
375
394
|
log(` ${dim('Config:')} ${MCP_CONFIG}`);
|
|
376
395
|
log(` ${dim('Server:')} ${actualServer}`);
|
|
@@ -386,11 +405,17 @@ function getClaudeMdContent() {
|
|
|
386
405
|
|
|
387
406
|
One workspace for every AI coding assistant.
|
|
388
407
|
|
|
408
|
+
## On every session start:
|
|
409
|
+
1. Call \`delimit_ledger_context\` to check for open tasks
|
|
410
|
+
2. If returning user, summarize what's pending
|
|
411
|
+
3. If new project, call \`delimit_scan\` to discover what Delimit can do
|
|
412
|
+
|
|
389
413
|
## Try these:
|
|
414
|
+
- "scan this project" -- discover what Delimit can do here
|
|
390
415
|
- "lint my API spec" -- catch breaking changes in your OpenAPI spec
|
|
391
416
|
- "add to ledger: [anything]" -- track tasks across sessions
|
|
392
417
|
- "what's on the ledger?" -- pick up where you left off
|
|
393
|
-
- "
|
|
418
|
+
- "deliberate [question]" -- get multi-model AI consensus
|
|
394
419
|
|
|
395
420
|
## What Delimit does:
|
|
396
421
|
- **API governance** -- lint, diff, semver classification, migration guides
|
package/gateway/ai/server.py
CHANGED
|
@@ -34,7 +34,11 @@ from fastmcp import FastMCP
|
|
|
34
34
|
logger = logging.getLogger("delimit.ai")
|
|
35
35
|
|
|
36
36
|
mcp = FastMCP("delimit")
|
|
37
|
-
mcp.description =
|
|
37
|
+
mcp.description = (
|
|
38
|
+
"Delimit — One workspace for every AI coding assistant. "
|
|
39
|
+
"On session start, call delimit_ledger_context to check for open tasks. "
|
|
40
|
+
"Use delimit_scan on new projects. Track all work via the ledger."
|
|
41
|
+
)
|
|
38
42
|
|
|
39
43
|
VERSION = "3.2.0"
|
|
40
44
|
|
|
@@ -2244,6 +2248,135 @@ def delimit_release_sync(action: str = "audit") -> Dict[str, Any]:
|
|
|
2244
2248
|
return _with_next_steps("release_sync", audit())
|
|
2245
2249
|
|
|
2246
2250
|
|
|
2251
|
+
@mcp.tool()
|
|
2252
|
+
def delimit_scan(project_path: str = ".") -> Dict[str, Any]:
|
|
2253
|
+
"""Scan a project and show what Delimit can do for it.
|
|
2254
|
+
|
|
2255
|
+
First-run discovery tool. Finds OpenAPI specs, checks for security issues,
|
|
2256
|
+
detects frameworks, and suggests what to track. Use this when you first
|
|
2257
|
+
install Delimit or open a new project.
|
|
2258
|
+
|
|
2259
|
+
Args:
|
|
2260
|
+
project_path: Path to the project to scan.
|
|
2261
|
+
"""
|
|
2262
|
+
import glob as _glob
|
|
2263
|
+
p = Path(project_path).resolve()
|
|
2264
|
+
findings = []
|
|
2265
|
+
suggestions = []
|
|
2266
|
+
|
|
2267
|
+
# 1. Find OpenAPI specs
|
|
2268
|
+
spec_patterns = ["**/openapi.yaml", "**/openapi.yml", "**/openapi.json",
|
|
2269
|
+
"**/swagger.yaml", "**/swagger.yml", "**/swagger.json",
|
|
2270
|
+
"**/*api*.yaml", "**/*api*.yml"]
|
|
2271
|
+
specs_found = []
|
|
2272
|
+
for pattern in spec_patterns:
|
|
2273
|
+
for match in p.glob(pattern):
|
|
2274
|
+
rel = str(match.relative_to(p))
|
|
2275
|
+
if "node_modules" not in rel and ".next" not in rel and "venv" not in rel:
|
|
2276
|
+
specs_found.append(rel)
|
|
2277
|
+
specs_found = list(set(specs_found))[:10]
|
|
2278
|
+
|
|
2279
|
+
if specs_found:
|
|
2280
|
+
findings.append({"type": "openapi_specs", "count": len(specs_found), "files": specs_found})
|
|
2281
|
+
suggestions.append({"action": "lint", "detail": f"Run delimit_lint on {specs_found[0]} to check for issues"})
|
|
2282
|
+
suggestions.append({"action": "github_action", "detail": "Add the Delimit GitHub Action to catch breaking changes on PRs"})
|
|
2283
|
+
else:
|
|
2284
|
+
# Check for framework that could generate a spec
|
|
2285
|
+
framework = None
|
|
2286
|
+
if (p / "requirements.txt").exists() or (p / "pyproject.toml").exists():
|
|
2287
|
+
for py_file in p.rglob("*.py"):
|
|
2288
|
+
if "node_modules" in str(py_file):
|
|
2289
|
+
continue
|
|
2290
|
+
try:
|
|
2291
|
+
content = py_file.read_text(errors="ignore")[:2000]
|
|
2292
|
+
if "FastAPI" in content or "fastapi" in content:
|
|
2293
|
+
framework = "FastAPI"
|
|
2294
|
+
break
|
|
2295
|
+
except Exception:
|
|
2296
|
+
pass
|
|
2297
|
+
if (p / "package.json").exists():
|
|
2298
|
+
try:
|
|
2299
|
+
pkg = json.loads((p / "package.json").read_text())
|
|
2300
|
+
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
|
|
2301
|
+
if "@nestjs/core" in deps:
|
|
2302
|
+
framework = "NestJS"
|
|
2303
|
+
elif "express" in deps:
|
|
2304
|
+
framework = "Express"
|
|
2305
|
+
except Exception:
|
|
2306
|
+
pass
|
|
2307
|
+
|
|
2308
|
+
if framework:
|
|
2309
|
+
findings.append({"type": "framework_detected", "framework": framework, "has_spec": False})
|
|
2310
|
+
suggestions.append({"action": "zero_spec", "detail": f"Run delimit_zero_spec to extract an OpenAPI spec from your {framework} code"})
|
|
2311
|
+
else:
|
|
2312
|
+
findings.append({"type": "no_api_detected", "note": "No OpenAPI spec or supported framework found"})
|
|
2313
|
+
|
|
2314
|
+
# 2. Check for security patterns (quick scan)
|
|
2315
|
+
security_issues = []
|
|
2316
|
+
for pattern_name, pattern_glob, check in [
|
|
2317
|
+
("env_file_in_git", ".env", lambda f: True),
|
|
2318
|
+
("hardcoded_key", "**/*.py", lambda f: "API_KEY" in f.read_text(errors="ignore")[:5000] and "os.environ" not in f.read_text(errors="ignore")[:5000]),
|
|
2319
|
+
("hardcoded_key_js", "**/*.js", lambda f: "apiKey" in f.read_text(errors="ignore")[:5000] and "process.env" not in f.read_text(errors="ignore")[:5000]),
|
|
2320
|
+
]:
|
|
2321
|
+
try:
|
|
2322
|
+
for match in p.glob(pattern_glob):
|
|
2323
|
+
rel = str(match.relative_to(p))
|
|
2324
|
+
if "node_modules" in rel or ".next" in rel or "venv" in rel or "__pycache__" in rel:
|
|
2325
|
+
continue
|
|
2326
|
+
if check(match):
|
|
2327
|
+
security_issues.append({"issue": pattern_name, "file": rel})
|
|
2328
|
+
break # One per pattern is enough
|
|
2329
|
+
except Exception:
|
|
2330
|
+
pass
|
|
2331
|
+
|
|
2332
|
+
if security_issues:
|
|
2333
|
+
findings.append({"type": "security_concerns", "count": len(security_issues), "issues": security_issues})
|
|
2334
|
+
suggestions.append({"action": "security_audit", "detail": "Run delimit_security_audit for a full scan"})
|
|
2335
|
+
|
|
2336
|
+
# 3. Check git status
|
|
2337
|
+
try:
|
|
2338
|
+
import subprocess
|
|
2339
|
+
result = subprocess.run(["git", "log", "--oneline", "-1"], capture_output=True, text=True, timeout=5, cwd=str(p))
|
|
2340
|
+
if result.returncode == 0:
|
|
2341
|
+
findings.append({"type": "git_repo", "latest_commit": result.stdout.strip()})
|
|
2342
|
+
except Exception:
|
|
2343
|
+
pass
|
|
2344
|
+
|
|
2345
|
+
# 4. Check for existing tests
|
|
2346
|
+
test_files = list(p.glob("**/test_*.py")) + list(p.glob("**/*.test.js")) + list(p.glob("**/*.test.ts")) + list(p.glob("**/*.spec.js"))
|
|
2347
|
+
test_files = [f for f in test_files if "node_modules" not in str(f)]
|
|
2348
|
+
if test_files:
|
|
2349
|
+
findings.append({"type": "tests_found", "count": len(test_files)})
|
|
2350
|
+
suggestions.append({"action": "test_coverage", "detail": "Run delimit_test_smoke to verify tests pass and measure coverage"})
|
|
2351
|
+
|
|
2352
|
+
# 5. Check ledger
|
|
2353
|
+
from ai.ledger_manager import list_items
|
|
2354
|
+
ledger = list_items(project_path=str(p))
|
|
2355
|
+
open_items = [i for i in ledger.get("items", []) if isinstance(i, dict) and i.get("status") == "open"]
|
|
2356
|
+
if open_items:
|
|
2357
|
+
findings.append({"type": "ledger_active", "open_items": len(open_items), "top": [i.get("title", "") for i in open_items[:3]]})
|
|
2358
|
+
else:
|
|
2359
|
+
suggestions.append({"action": "ledger", "detail": "Say 'add to ledger: [task]' to start tracking work across sessions"})
|
|
2360
|
+
|
|
2361
|
+
# 6. Check deliberation models
|
|
2362
|
+
from ai.deliberation import get_models_config
|
|
2363
|
+
models = get_models_config()
|
|
2364
|
+
enabled = [v.get("name", k) for k, v in models.items() if v.get("enabled")]
|
|
2365
|
+
if len(enabled) >= 2:
|
|
2366
|
+
findings.append({"type": "deliberation_ready", "models": enabled})
|
|
2367
|
+
elif len(enabled) == 1:
|
|
2368
|
+
suggestions.append({"action": "models", "detail": f"Add 1 more AI model for multi-model deliberation (have {enabled[0]})"})
|
|
2369
|
+
else:
|
|
2370
|
+
suggestions.append({"action": "models", "detail": "Configure AI models for deliberation: say 'configure delimit models'"})
|
|
2371
|
+
|
|
2372
|
+
return _with_next_steps("scan", {
|
|
2373
|
+
"project": str(p),
|
|
2374
|
+
"findings": findings,
|
|
2375
|
+
"suggestions": suggestions,
|
|
2376
|
+
"summary": f"Found {len(findings)} things, {len(suggestions)} suggestions",
|
|
2377
|
+
})
|
|
2378
|
+
|
|
2379
|
+
|
|
2247
2380
|
# ═══════════════════════════════════════════════════════════════════════
|
|
2248
2381
|
# ENTRY POINT
|
|
2249
2382
|
# ═══════════════════════════════════════════════════════════════════════
|
package/package.json
CHANGED