delimit-cli 3.13.3 → 3.14.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.
@@ -5,6 +5,7 @@ const axios = require('axios');
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
  const { execSync, spawn } = require('child_process');
8
+ const os = require('os');
8
9
  const chalk = require('chalk');
9
10
  const inquirer = require('inquirer');
10
11
  const DelimitAuthSetup = require('../lib/auth-setup');
@@ -1067,9 +1068,43 @@ program
1067
1068
  return;
1068
1069
  }
1069
1070
 
1071
+ // Step 2b: Compliance template selection (LED-258)
1072
+ let complianceTemplate = null;
1073
+ if (!options.yes) {
1074
+ try {
1075
+ const templateAns = await inquirer.prompt([{
1076
+ type: 'list',
1077
+ name: 'template',
1078
+ message: 'Compliance template (optional):',
1079
+ choices: [
1080
+ { name: 'none — Standard governance only', value: 'none' },
1081
+ { name: 'SOC2 — Service Organization Control evidence', value: 'soc2' },
1082
+ { name: 'PCI-DSS — Payment card data protection', value: 'pci-dss' },
1083
+ { name: 'HIPAA — Healthcare data safeguards', value: 'hipaa' },
1084
+ { name: 'startup — Fast-moving team defaults', value: 'startup' },
1085
+ ],
1086
+ default: 'none',
1087
+ }]);
1088
+ if (templateAns.template !== 'none') complianceTemplate = templateAns.template;
1089
+ } catch {}
1090
+ }
1091
+
1070
1092
  // Step 3: Create policy file
1071
1093
  fs.mkdirSync(configDir, { recursive: true });
1072
1094
  fs.writeFileSync(policyFile, POLICY_PRESETS[preset]);
1095
+
1096
+ // Write compliance template config if selected
1097
+ if (complianceTemplate) {
1098
+ const templateConfig = {
1099
+ soc2: { evidence_required: true, audit_trail: true, change_approval: true, retention_days: 365 },
1100
+ 'pci-dss': { evidence_required: true, audit_trail: true, change_approval: true, secret_scanning: true, retention_days: 365 },
1101
+ hipaa: { evidence_required: true, audit_trail: true, change_approval: true, phi_detection: true, retention_days: 2190 },
1102
+ startup: { evidence_required: false, audit_trail: true, change_approval: false, retention_days: 90 },
1103
+ };
1104
+ const tmplFile = path.join(configDir, 'compliance.json');
1105
+ fs.writeFileSync(tmplFile, JSON.stringify({ template: complianceTemplate, ...templateConfig[complianceTemplate] }, null, 2));
1106
+ console.log(chalk.green(` Created .delimit/compliance.json (${complianceTemplate})`));
1107
+ }
1073
1108
  console.log(chalk.green(` Created .delimit/policies.yml (${preset})`));
1074
1109
 
1075
1110
  // Step 4: Add GitHub Action workflow if spec found + GitHub CI
@@ -1130,6 +1165,69 @@ jobs:
1130
1165
  }
1131
1166
  }
1132
1167
 
1168
+ // Step 4b: Add scheduled drift monitoring workflow (LED-260)
1169
+ if (specPath && ciProvider === 'github') {
1170
+ const driftWorkflowFile = path.join(projectDir, '.github', 'workflows', 'api-drift-monitor.yml');
1171
+ if (!fs.existsSync(driftWorkflowFile)) {
1172
+ let writeDrift = false;
1173
+ if (!options.yes) {
1174
+ try {
1175
+ const driftAns = await inquirer.prompt([{
1176
+ type: 'confirm',
1177
+ name: 'addDrift',
1178
+ message: 'Add weekly drift monitoring workflow?',
1179
+ default: true,
1180
+ }]);
1181
+ writeDrift = driftAns.addDrift;
1182
+ } catch {}
1183
+ }
1184
+ if (writeDrift) {
1185
+ try {
1186
+ const driftContent = `name: API Drift Monitor
1187
+ on:
1188
+ schedule:
1189
+ - cron: '17 9 * * 1' # Weekly on Monday at 9:17 AM UTC
1190
+ workflow_dispatch: {}
1191
+
1192
+ permissions:
1193
+ contents: read
1194
+ issues: write
1195
+
1196
+ jobs:
1197
+ drift-check:
1198
+ runs-on: ubuntu-latest
1199
+ steps:
1200
+ - uses: actions/checkout@v4
1201
+ - uses: actions/setup-python@v4
1202
+ with:
1203
+ python-version: '3.10'
1204
+ - run: pip install pyyaml 2>/dev/null
1205
+ - name: Check for API drift
1206
+ run: |
1207
+ npx delimit-cli lint ${specPath} ${specPath} --baseline
1208
+ echo "Drift check complete"
1209
+ - name: Create issue on drift
1210
+ if: failure()
1211
+ uses: actions/github-script@v6
1212
+ with:
1213
+ script: |
1214
+ await github.rest.issues.create({
1215
+ owner: context.repo.owner,
1216
+ repo: context.repo.repo,
1217
+ title: 'API Drift Detected — Governance Review Needed',
1218
+ body: 'The weekly drift monitor detected changes to the API spec that have not been reviewed through governance. Run delimit lint to review.',
1219
+ labels: ['api-governance', 'drift'],
1220
+ });
1221
+ `;
1222
+ fs.writeFileSync(driftWorkflowFile, driftContent);
1223
+ console.log(chalk.green(' Created .github/workflows/api-drift-monitor.yml'));
1224
+ } catch (err) {
1225
+ console.log(chalk.yellow(` Could not write drift workflow: ${err.message}`));
1226
+ }
1227
+ }
1228
+ }
1229
+ }
1230
+
1133
1231
  // Step 5: Run first lint to show immediate value
1134
1232
  console.log('');
1135
1233
  if (specPath) {
@@ -1182,9 +1280,50 @@ jobs:
1182
1280
  }
1183
1281
  }
1184
1282
 
1283
+ // Step 6: Save first evidence event (LED-258)
1284
+ const evidenceDir = path.join(configDir, 'evidence');
1285
+ fs.mkdirSync(evidenceDir, { recursive: true });
1286
+ const evidenceEvent = {
1287
+ id: `EVD-${Date.now().toString(36)}`,
1288
+ ts: new Date().toISOString(),
1289
+ type: 'governance_init',
1290
+ tool: 'delimit_init',
1291
+ model: 'cli',
1292
+ status: 'pass',
1293
+ summary: `Governance initialized with ${preset} preset`,
1294
+ detail: [
1295
+ `Project: ${projectName}`,
1296
+ frameworkLabel ? `Framework: ${frameworkLabel}` : null,
1297
+ specPath ? `Spec: ${specPath}` : 'Mode: Zero-Spec',
1298
+ `Preset: ${preset}`,
1299
+ ciProvider !== 'none' ? `CI: ${ciProvider}` : null,
1300
+ ].filter(Boolean).join('\n'),
1301
+ venture: projectName,
1302
+ };
1303
+ try {
1304
+ const evidenceFile = path.join(evidenceDir, 'events.jsonl');
1305
+ fs.appendFileSync(evidenceFile, JSON.stringify(evidenceEvent) + '\n');
1306
+ console.log(chalk.green(' Evidence recorded — first governance event saved'));
1307
+ } catch {}
1308
+
1309
+ // Step 7: Show gate status (LED-258)
1310
+ console.log(chalk.bold('\n Governance Gates:'));
1311
+ const gates = [
1312
+ { name: 'API Lint', status: specPath || ['fastapi', 'nestjs', 'express'].includes(framework) ? 'active' : 'inactive', chain: 'semver → gov_evaluate' },
1313
+ { name: 'Security Audit', status: 'ready', chain: 'evidence_collect → notify' },
1314
+ { name: 'Deploy Plan', status: 'ready', chain: 'security_audit' },
1315
+ { name: 'Release Validate', status: 'ready', chain: 'evidence_collect → notify → ledger' },
1316
+ ];
1317
+ for (const gate of gates) {
1318
+ const icon = gate.status === 'active' ? chalk.green('●') : gate.status === 'ready' ? chalk.yellow('○') : chalk.gray('○');
1319
+ const statusLabel = gate.status === 'active' ? chalk.green('active') : gate.status === 'ready' ? chalk.yellow('ready') : chalk.gray('inactive');
1320
+ console.log(` ${icon} ${chalk.bold(gate.name)} ${chalk.gray('→')} ${chalk.gray(gate.chain)} ${chalk.gray('(')}${statusLabel}${chalk.gray(')')}`);
1321
+ }
1322
+
1185
1323
  // Summary
1186
1324
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
1187
- console.log(chalk.bold(`\n Setup complete in ${elapsed}s\n`));
1325
+ console.log(chalk.bold(`\n Setup complete in ${elapsed}s`));
1326
+ console.log(chalk.gray(` Evidence saved to .delimit/evidence/events.jsonl\n`));
1188
1327
  console.log(' Next steps:');
1189
1328
  if (specPath) {
1190
1329
  console.log(` ${chalk.bold('delimit lint')} ${specPath} ${specPath} — lint on every PR`);
@@ -1196,6 +1335,170 @@ jobs:
1196
1335
  console.log('');
1197
1336
  });
1198
1337
 
1338
+ // Demo command — prove governance value in 5 minutes (LED-262)
1339
+ program
1340
+ .command('demo')
1341
+ .description('Run a self-contained governance demo — proves value in under 5 minutes')
1342
+ .action(async () => {
1343
+ const tmpDir = path.join(os.tmpdir(), `delimit-demo-${Date.now()}`);
1344
+ fs.mkdirSync(tmpDir, { recursive: true });
1345
+
1346
+ console.log(chalk.bold('\n Delimit Governance Demo\n'));
1347
+ console.log(chalk.gray(` Working in ${tmpDir}\n`));
1348
+
1349
+ // Step 1: Create a sample API spec
1350
+ console.log(chalk.bold(' Step 1: Creating sample API spec...'));
1351
+ const baseSpec = {
1352
+ openapi: '3.0.3',
1353
+ info: { title: 'Pet Store API', version: '1.0.0' },
1354
+ paths: {
1355
+ '/pets': {
1356
+ get: {
1357
+ summary: 'List all pets',
1358
+ operationId: 'listPets',
1359
+ parameters: [
1360
+ { name: 'limit', in: 'query', required: false, schema: { type: 'integer' } },
1361
+ ],
1362
+ responses: { '200': { description: 'A list of pets', content: { 'application/json': { schema: { type: 'array', items: { '$ref': '#/components/schemas/Pet' } } } } } },
1363
+ },
1364
+ post: {
1365
+ summary: 'Create a pet',
1366
+ operationId: 'createPet',
1367
+ requestBody: { content: { 'application/json': { schema: { '$ref': '#/components/schemas/Pet' } } } },
1368
+ responses: { '201': { description: 'Pet created' } },
1369
+ },
1370
+ },
1371
+ '/pets/{petId}': {
1372
+ get: {
1373
+ summary: 'Get a pet by ID',
1374
+ operationId: 'showPetById',
1375
+ parameters: [{ name: 'petId', in: 'path', required: true, schema: { type: 'string' } }],
1376
+ responses: { '200': { description: 'A pet', content: { 'application/json': { schema: { '$ref': '#/components/schemas/Pet' } } } } },
1377
+ },
1378
+ },
1379
+ },
1380
+ components: {
1381
+ schemas: {
1382
+ Pet: {
1383
+ type: 'object',
1384
+ required: ['id', 'name'],
1385
+ properties: {
1386
+ id: { type: 'integer', format: 'int64' },
1387
+ name: { type: 'string' },
1388
+ tag: { type: 'string' },
1389
+ },
1390
+ },
1391
+ },
1392
+ },
1393
+ };
1394
+
1395
+ const baseSpecPath = path.join(tmpDir, 'openapi-v1.yaml');
1396
+ fs.writeFileSync(baseSpecPath, yaml.dump(baseSpec));
1397
+ console.log(chalk.green(' Created openapi-v1.yaml (3 endpoints, 1 schema)\n'));
1398
+
1399
+ // Step 2: Introduce breaking changes
1400
+ console.log(chalk.bold(' Step 2: Introducing breaking changes...'));
1401
+ const changedSpec = JSON.parse(JSON.stringify(baseSpec));
1402
+ changedSpec.info.version = '2.0.0';
1403
+
1404
+ // Breaking: remove endpoint
1405
+ delete changedSpec.paths['/pets/{petId}'];
1406
+ // Breaking: add required parameter
1407
+ changedSpec.paths['/pets'].get.parameters.push(
1408
+ { name: 'owner_id', in: 'query', required: true, schema: { type: 'string' } }
1409
+ );
1410
+ // Breaking: remove response field
1411
+ delete changedSpec.components.schemas.Pet.properties.tag;
1412
+ // Non-breaking: add endpoint
1413
+ changedSpec.paths['/pets/search'] = {
1414
+ get: {
1415
+ summary: 'Search pets',
1416
+ operationId: 'searchPets',
1417
+ parameters: [{ name: 'q', in: 'query', required: true, schema: { type: 'string' } }],
1418
+ responses: { '200': { description: 'Search results' } },
1419
+ },
1420
+ };
1421
+
1422
+ const changedSpecPath = path.join(tmpDir, 'openapi-v2.yaml');
1423
+ fs.writeFileSync(changedSpecPath, yaml.dump(changedSpec));
1424
+ console.log(chalk.red(' Removed: GET /pets/{petId}'));
1425
+ console.log(chalk.red(' Added required param: owner_id on GET /pets'));
1426
+ console.log(chalk.red(' Removed field: Pet.tag'));
1427
+ console.log(chalk.green(' Added: GET /pets/search'));
1428
+ console.log('');
1429
+
1430
+ // Step 3: Run Delimit lint
1431
+ console.log(chalk.bold(' Step 3: Running governance check...\n'));
1432
+ try {
1433
+ const result = apiEngine.lint(baseSpecPath, changedSpecPath, { policy: 'strict' });
1434
+
1435
+ if (result && result.summary) {
1436
+ const s = result.summary;
1437
+ const breaking = s.breaking || s.breaking_changes || 0;
1438
+ const total = s.total || s.total_changes || 0;
1439
+ const safe = total - breaking;
1440
+
1441
+ if (breaking > 0) {
1442
+ console.log(chalk.red.bold(` BLOCKED — ${breaking} breaking change(s) detected\n`));
1443
+ } else {
1444
+ console.log(chalk.green.bold(` PASSED — No breaking changes\n`));
1445
+ }
1446
+
1447
+ // Show violations
1448
+ const violations = result.violations || [];
1449
+ if (violations.length > 0) {
1450
+ console.log(chalk.bold(' Violations:'));
1451
+ violations.forEach((v, i) => {
1452
+ const icon = v.severity === 'error' ? chalk.red(' BLOCK') : chalk.yellow(' WARN ');
1453
+ console.log(` ${icon} ${v.message}`);
1454
+ if (v.path) console.log(chalk.gray(` ${v.path}`));
1455
+ });
1456
+ console.log('');
1457
+ }
1458
+
1459
+ // Semver
1460
+ if (result.semver) {
1461
+ console.log(` Semver: ${chalk.bold(result.semver.bump?.toUpperCase() || 'MAJOR')}`);
1462
+ if (result.semver.next_version) {
1463
+ console.log(` Next version: ${chalk.bold(result.semver.next_version)}`);
1464
+ }
1465
+ }
1466
+
1467
+ // Show safe changes
1468
+ if (safe > 0) {
1469
+ console.log(chalk.green(`\n ${safe} additive change(s) also detected`));
1470
+ }
1471
+ }
1472
+ } catch (err) {
1473
+ console.log(chalk.yellow(` Lint error: ${err.message}`));
1474
+ }
1475
+
1476
+ // Step 4: Show governance gates
1477
+ console.log(chalk.bold('\n Governance Gates:'));
1478
+ console.log(` ${chalk.red('X')} API Lint ${chalk.gray('→ semver → gov_evaluate')}`);
1479
+ console.log(` ${chalk.red('X')} Policy Compliance ${chalk.gray('→ evidence_collect')}`);
1480
+ console.log(` ${chalk.green('+')} Security Audit ${chalk.gray('→ evidence_collect → notify')}`);
1481
+ console.log(` ${chalk.red('X')} Deploy Readiness ${chalk.gray('→ deploy_plan → security_audit')}`);
1482
+ console.log(chalk.red.bold('\n Deploy BLOCKED until all gates pass.\n'));
1483
+
1484
+ // Step 5: Show what would happen with the fix
1485
+ console.log(chalk.bold(' What Delimit does:'));
1486
+ console.log(chalk.gray(' 1. Detects the 3 breaking changes automatically'));
1487
+ console.log(chalk.gray(' 2. Evaluates against your policy (strict/default/relaxed)'));
1488
+ console.log(chalk.gray(' 3. Blocks the deploy via governance gates'));
1489
+ console.log(chalk.gray(' 4. Records evidence for audit trail'));
1490
+ console.log(chalk.gray(' 5. Posts remediation guide on the PR'));
1491
+ console.log(chalk.gray(' 6. Tracks in the ledger for cross-model continuity'));
1492
+
1493
+ console.log(chalk.bold('\n Try it on your project:'));
1494
+ console.log(` ${chalk.green('npx delimit-cli init')} — set up governance`);
1495
+ console.log(` ${chalk.green('npx delimit-cli lint')} — lint your API spec`);
1496
+ console.log(` ${chalk.green('npx delimit-cli setup')} — configure AI assistants\n`);
1497
+
1498
+ // Cleanup
1499
+ try { fs.rmSync(tmpDir, { recursive: true }); } catch {}
1500
+ });
1501
+
1199
1502
  // Doctor command — verify setup is correct
1200
1503
  program
1201
1504
  .command('doctor')
@@ -290,7 +290,7 @@ async function main() {
290
290
  }
291
291
  // Add governance instructions
292
292
  if (!geminiConfig.customInstructions || !geminiConfig.customInstructions.includes('delimit_ledger_context')) {
293
- geminiConfig.customInstructions = 'Delimit MCP is installed. On every session start, call delimit_ledger_context to check for open tasks. If new project, call delimit_scan. Track all work via the ledger.';
293
+ geminiConfig.customInstructions = 'Delimit MCP is installed. On every session start: (1) call delimit_ledger_context for open tasks, (2) call delimit_gov_health, (3) call delimit_inbox_daemon(action="start") to start email polling. If new project, call delimit_scan. Track all work via the ledger.';
294
294
  fs.writeFileSync(GEMINI_CONFIG, JSON.stringify(geminiConfig, null, 2));
295
295
  }
296
296
  } catch (e) {
@@ -537,7 +537,12 @@ if [ "$DELIMIT_WRAPPED" = "true" ] || [ ! -t 1 ]; then
537
537
  fi
538
538
  DELIMIT_HOME="\${DELIMIT_HOME:-$HOME/.delimit}"
539
539
  TOOL_COUNT="0"
540
- [ -f "$DELIMIT_HOME/server/ai/server.py" ] && TOOL_COUNT=$(grep -c '@mcp.tool' "$DELIMIT_HOME/server/ai/server.py" 2>/dev/null || echo "0")
540
+ if [ -f "$DELIMIT_HOME/server/ai/server.py" ]; then
541
+ TOTAL=$(grep -c '@mcp.tool' "$DELIMIT_HOME/server/ai/server.py" 2>/dev/null || echo "0")
542
+ OPS=$(grep -c '@_ops_pack_tool' "$DELIMIT_HOME/server/ai/server.py" 2>/dev/null || echo "0")
543
+ INTERNAL=$(grep -c '@_internal_tool' "$DELIMIT_HOME/server/ai/server.py" 2>/dev/null || echo "0")
544
+ TOOL_COUNT=$((TOTAL - OPS - INTERNAL))
545
+ fi
541
546
  echo ""
542
547
  printf " \${PURPLE}\${BOLD} ____ ________ ______ _____________\${RESET}\\n"
543
548
  printf " \${PURPLE}\${BOLD} / __ \\\\/ ____/ / / _/ |/ / _/_ __/\${RESET}\\n"
@@ -810,8 +815,9 @@ Unify all AI coding assistants with persistent context, governance, and multi-mo
810
815
  ## On every session start:
811
816
  1. Call \`delimit_ledger_context\` to check for open tasks
812
817
  2. Call \`delimit_gov_health\` to check governance status
813
- 3. If returning user, summarize what's pending
814
- 4. If new project, call \`delimit_scan\` to discover what Delimit can do
818
+ 3. Call \`delimit_inbox_daemon(action="start")\` to start email polling — ALL venture inboxes route through this. Owner approves/adjusts/denies via email reply, same as being logged in.
819
+ 4. If returning user, summarize what's pending
820
+ 5. If new project, call \`delimit_scan\` to discover what Delimit can do
815
821
 
816
822
  ## After editing code:
817
823
  - After editing UI/CSS: call \`delimit_design_validate_responsive\`