delimit-cli 4.1.40 → 4.1.42

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 CHANGED
@@ -169,6 +169,7 @@ npx delimit-cli recall --export # Export as markdown
169
169
  npx delimit-cli forget abc123 # Delete a memory by ID
170
170
  npx delimit-cli models # Configure deliberation API keys (BYOK wizard)
171
171
  npx delimit-cli models --status # Show current model config
172
+ npx delimit-cli status # Compact dashboard of your Delimit setup
172
173
  npx delimit-cli doctor # Check setup health
173
174
  npx delimit-cli uninstall --dry-run # Preview removal
174
175
  ```
@@ -128,6 +128,20 @@ if (process.env.DELIMIT_DEBUG_CONTINUITY === '1') {
128
128
  console.log('');
129
129
  }
130
130
 
131
+ // Helper to format a timestamp as relative time (e.g. "2h ago", "3d ago")
132
+ function _relativeTime(ts) {
133
+ const diff = Date.now() - ts;
134
+ const mins = Math.floor(diff / 60000);
135
+ if (mins < 1) return 'just now';
136
+ if (mins < 60) return mins + 'm ago';
137
+ const hrs = Math.floor(mins / 60);
138
+ if (hrs < 24) return hrs + 'h ago';
139
+ const days = Math.floor(hrs / 24);
140
+ if (days < 30) return days + 'd ago';
141
+ const months = Math.floor(days / 30);
142
+ return months + 'mo ago';
143
+ }
144
+
131
145
  // Helper to check if agent is running
132
146
  async function checkAgent() {
133
147
  try {
@@ -311,99 +325,193 @@ program
311
325
  // Status command
312
326
  program
313
327
  .command('status')
314
- .description('Show governance status')
328
+ .description('Show a compact dashboard of your Delimit setup')
315
329
 
316
330
  .option('--verbose', 'Show detailed status')
317
331
  .action(async (options) => {
318
- const agentRunning = await checkAgent();
319
-
320
- console.log(chalk.blue.bold('\nDelimit Governance Status\n'));
321
- console.log('Agent:', agentRunning ? chalk.green('✓ Running') : chalk.red('✗ Not running'));
332
+ const homedir = os.homedir();
333
+ const delimitHome = path.join(homedir, '.delimit');
334
+ const target = process.cwd();
335
+
336
+ console.log(chalk.bold('\n Delimit Status\n'));
337
+
338
+ // --- Memory stats ---
339
+ const memoryDir = path.join(delimitHome, 'memory');
340
+ let memTotal = 0;
341
+ let memRecent = 0;
342
+ let recentMemories = [];
343
+ const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
344
+ try {
345
+ const memFiles = fs.readdirSync(memoryDir).filter(f => f.startsWith('mem-') && f.endsWith('.json'));
346
+ memTotal = memFiles.length;
347
+ for (const f of memFiles) {
348
+ try {
349
+ const data = JSON.parse(fs.readFileSync(path.join(memoryDir, f), 'utf-8'));
350
+ const ts = new Date(data.created_at || data.timestamp || data.created || 0).getTime();
351
+ if (ts > oneWeekAgo) memRecent++;
352
+ recentMemories.push({ text: data.text || data.content || '', tags: data.tags || [], ts });
353
+ } catch {}
354
+ }
355
+ recentMemories.sort((a, b) => b.ts - a.ts);
356
+ recentMemories = recentMemories.slice(0, 3);
357
+ } catch {}
358
+ console.log(` Memory: ${chalk.white.bold(memTotal)} memories${memRecent > 0 ? ` (${memRecent} this week)` : ''}`);
322
359
 
323
- if (options.verbose) {
324
- console.log('\n' + chalk.bold('Continuity Context:'));
325
- console.log(formatContinuityReport(continuityContext).split('\n').slice(1).map(line => ' ' + line.trimStart()).join('\n'));
326
- }
327
-
328
- if (agentRunning) {
329
- const { data } = await axios.get(`${AGENT_URL}/status`);
330
-
331
- // Mode information
332
- console.log('\n' + chalk.bold('Mode Configuration:'));
333
- console.log(` Current Mode: ${chalk.bold(data.sessionMode)}`);
334
- if (data.defaultMode) {
335
- console.log(` Default Mode: ${data.defaultMode}`);
360
+ // --- Governance / Policy ---
361
+ const policyPath = path.join(target, '.delimit', 'policies.yml');
362
+ let policyLabel = chalk.gray('none');
363
+ let hasPolicy = false;
364
+ if (fs.existsSync(policyPath)) {
365
+ hasPolicy = true;
366
+ try {
367
+ const policyContent = yaml.load(fs.readFileSync(policyPath, 'utf-8'));
368
+ const preset = policyContent?.preset || policyContent?.name || 'custom';
369
+ policyLabel = chalk.green(preset + ' policy');
370
+ } catch {
371
+ policyLabel = chalk.green('custom policy');
336
372
  }
337
- if (data.effectiveMode && data.effectiveMode !== data.sessionMode) {
338
- console.log(` Effective Mode: ${chalk.yellow(data.effectiveMode)} (escalated)`);
373
+ }
374
+ // Count tracked specs
375
+ const specPatterns = ['openapi.yaml', 'openapi.yml', 'openapi.json', 'swagger.yaml', 'swagger.yml', 'swagger.json'];
376
+ let specCount = 0;
377
+ const _countSpecs = (dir, depth) => {
378
+ if (depth > 3) return;
379
+ try {
380
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
381
+ if (['node_modules', '.next', 'venv', '.git'].includes(entry.name)) continue;
382
+ const full = path.join(dir, entry.name);
383
+ if (entry.isFile() && specPatterns.includes(entry.name.toLowerCase())) {
384
+ specCount++;
385
+ } else if (entry.isDirectory()) {
386
+ _countSpecs(full, depth + 1);
387
+ }
388
+ }
389
+ } catch {}
390
+ };
391
+ _countSpecs(target, 0);
392
+ const specLabel = specCount > 0 ? `${specCount} spec${specCount > 1 ? 's' : ''} tracked` : chalk.gray('no specs');
393
+ console.log(` Governance: ${policyLabel}${hasPolicy ? ' | ' : ' | '}${specLabel}`);
394
+
395
+ // --- Git hooks ---
396
+ const preCommitPath = path.join(target, '.git', 'hooks', 'pre-commit');
397
+ let hasGitHooks = false;
398
+ try {
399
+ const hookContent = fs.readFileSync(preCommitPath, 'utf-8');
400
+ hasGitHooks = hookContent.includes('delimit');
401
+ } catch {}
402
+ console.log(` Git Hooks: ${hasGitHooks ? chalk.green('pre-commit installed') : chalk.gray('not installed')}`);
403
+
404
+ // --- CI ---
405
+ const workflowPath = path.join(target, '.github', 'workflows', 'api-governance.yml');
406
+ const hasCI = fs.existsSync(workflowPath);
407
+ console.log(` CI: ${hasCI ? chalk.green('GitHub Action active') : chalk.gray('not configured')}`);
408
+
409
+ // --- MCP ---
410
+ const mcpConfigPath = path.join(homedir, '.mcp.json');
411
+ let hasMcp = false;
412
+ let toolCount = 0;
413
+ try {
414
+ const mcpContent = fs.readFileSync(mcpConfigPath, 'utf-8');
415
+ hasMcp = mcpContent.includes('delimit');
416
+ } catch {}
417
+ if (hasMcp) {
418
+ // Count tools from server.py if available
419
+ const serverPyPaths = [
420
+ path.join(delimitHome, 'server', 'ai', 'server.py'),
421
+ path.join(delimitHome, 'server', 'server.py'),
422
+ ];
423
+ for (const sp of serverPyPaths) {
424
+ try {
425
+ const serverContent = fs.readFileSync(sp, 'utf-8');
426
+ const toolMatches = serverContent.match(/@mcp\.tool/g);
427
+ if (toolMatches) {
428
+ toolCount = toolMatches.length;
429
+ break;
430
+ }
431
+ } catch {}
339
432
  }
340
-
341
- // Policies
342
- console.log('\n' + chalk.bold('Policies:'));
343
- if (data.policiesLoaded.length > 0) {
344
- data.policiesLoaded.forEach(policy => {
345
- console.log(` • ${policy}`);
346
- });
347
- if (data.totalRules) {
348
- console.log(` Total Rules: ${data.totalRules}`);
433
+ console.log(` MCP: ${chalk.green('connected')}${toolCount > 0 ? ` (${toolCount} tools)` : ''}`);
434
+ } else {
435
+ console.log(` MCP: ${chalk.gray('not configured')}`);
436
+ }
437
+
438
+ // --- Models ---
439
+ const modelsPath = path.join(delimitHome, 'models.json');
440
+ let modelsLabel = chalk.gray('none configured');
441
+ try {
442
+ const modelsData = JSON.parse(fs.readFileSync(modelsPath, 'utf-8'));
443
+ const modelNames = [];
444
+ for (const [key, val] of Object.entries(modelsData)) {
445
+ if (val && typeof val === 'object' && (val.api_key || val.enabled !== false)) {
446
+ modelNames.push(key.charAt(0).toUpperCase() + key.slice(1));
349
447
  }
350
- } else {
351
- console.log(' No policies loaded');
352
448
  }
353
-
354
- // Recent activity
355
- console.log('\n' + chalk.bold('Activity:'));
356
- console.log(` Audit Log Entries: ${data.auditLogSize}`);
357
- if (data.lastDecision) {
358
- const timeSince = Date.now() - new Date(data.lastDecision.timestamp);
359
- const minutes = Math.floor(timeSince / 60000);
360
- console.log(` Last Decision: ${minutes} minutes ago (${data.lastDecision.action})`);
449
+ if (modelNames.length > 0) {
450
+ modelsLabel = chalk.white(modelNames.join(' + ')) + chalk.gray(' (BYOK)');
361
451
  }
362
- console.log(` Uptime: ${Math.floor(data.uptime / 60)} minutes`);
363
-
364
- // Verbose mode shows recent decisions
365
- if (options.verbose && data.recentDecisions) {
366
- console.log('\n' + chalk.bold('Recent Decisions:'));
367
- data.recentDecisions.forEach(decision => {
368
- const color = decision.action === 'block' ? chalk.red :
369
- decision.action === 'prompt' ? chalk.yellow :
370
- chalk.green;
371
- console.log(` ${decision.timestamp} | ${color(decision.mode)} | ${decision.rule || 'no rule'}`);
372
- });
452
+ } catch {}
453
+ console.log(` Models: ${modelsLabel}`);
454
+
455
+ // --- License ---
456
+ const licensePath = path.join(delimitHome, 'license.json');
457
+ let licenseLabel = chalk.gray('Free');
458
+ try {
459
+ const licenseData = JSON.parse(fs.readFileSync(licensePath, 'utf-8'));
460
+ const tier = licenseData.tier || licenseData.plan || 'Free';
461
+ const active = licenseData.status === 'active' || licenseData.valid === true;
462
+ if (tier.toLowerCase() !== 'free') {
463
+ licenseLabel = active ? chalk.green(`${tier} (active)`) : chalk.yellow(`${tier} (${licenseData.status || 'unknown'})`);
464
+ }
465
+ } catch {}
466
+ console.log(` License: ${licenseLabel}`);
467
+
468
+ // --- Recent memories ---
469
+ if (recentMemories.length > 0) {
470
+ console.log(chalk.bold('\n Recent memories:'));
471
+ for (const mem of recentMemories) {
472
+ const ago = _relativeTime(mem.ts);
473
+ const tagStr = mem.tags.length > 0 ? ' ' + chalk.gray(mem.tags.map(t => '#' + t).join(' ')) : '';
474
+ const text = mem.text.length > 55 ? mem.text.slice(0, 55) + '...' : mem.text;
475
+ console.log(` ${chalk.gray('[' + ago + ']')} ${text}${tagStr}`);
373
476
  }
374
477
  }
375
-
376
- // System integration
377
- console.log('\n' + chalk.bold('System Integration:'));
378
-
379
- // Git hooks
478
+
479
+ // --- Last session ---
480
+ const sessionsDir = path.join(delimitHome, 'sessions');
380
481
  try {
381
- const hooksPath = execSync('git config --global core.hooksPath').toString().trim();
382
- const hooksActive = hooksPath.includes('.delimit');
383
- console.log(` Git Hooks: ${hooksActive ? chalk.green('✓ Active') : chalk.yellow('⚠ Not configured')}`);
384
- } catch (e) {
385
- console.log(` Git Hooks: ${chalk.red('✗ Not configured')}`);
386
- }
387
-
388
- // PATH
389
- if (process.env.PATH.includes('.delimit/shims')) {
390
- console.log(` AI Tool Interception: ${chalk.green(' Active')}`);
391
- } else {
392
- console.log(` AI Tool Interception: ${chalk.gray('Not active')}`);
393
- }
394
-
395
- // Policy files
396
- const policyFiles = [];
397
- if (fs.existsSync('delimit.yml')) {
398
- policyFiles.push('project');
399
- }
400
- if (fs.existsSync(path.join(process.env.HOME, '.config', 'delimit', 'delimit.yml'))) {
401
- policyFiles.push('user');
402
- }
403
- console.log(` Policy Files: ${policyFiles.length > 0 ? policyFiles.join(', ') : chalk.gray('none')}`);
404
-
482
+ const sessFiles = fs.readdirSync(sessionsDir)
483
+ .filter(f => f.startsWith('session_') && f.endsWith('.json'))
484
+ .sort()
485
+ .reverse();
486
+ if (sessFiles.length > 0) {
487
+ const latest = JSON.parse(fs.readFileSync(path.join(sessionsDir, sessFiles[0]), 'utf-8'));
488
+ const summary = latest.summary || latest.description || latest.title || null;
489
+ if (summary) {
490
+ const truncated = summary.length > 60 ? summary.slice(0, 60) + '...' : summary;
491
+ console.log(chalk.bold('\n Last session: ') + truncated);
492
+ }
493
+ }
494
+ } catch {}
495
+
496
+ // --- Governance readiness ---
497
+ const hasSpecs = specCount > 0;
498
+ const checks = [
499
+ { name: 'API spec', done: hasSpecs },
500
+ { name: 'Policy', done: hasPolicy },
501
+ { name: 'CI gate', done: hasCI },
502
+ { name: 'Git hooks', done: hasGitHooks },
503
+ { name: 'MCP', done: hasMcp },
504
+ ];
505
+ const score = checks.filter(c => c.done).length;
506
+
507
+ console.log(chalk.bold(`\n Governance readiness: ${score}/${checks.length}`));
508
+ console.log(' ' + checks.map(c => c.done ? chalk.green('\u25cf') + ' ' + c.name : chalk.gray('\u25cb') + ' ' + chalk.gray(c.name)).join(' '));
509
+ console.log('');
510
+
405
511
  if (options.verbose) {
406
- console.log('\n' + chalk.gray('Run "delimit doctor" for detailed diagnostics'));
512
+ console.log(chalk.bold(' Continuity Context:'));
513
+ console.log(formatContinuityReport(continuityContext).split('\n').slice(1).map(line => ' ' + line.trimStart()).join('\n'));
514
+ console.log('');
407
515
  }
408
516
  });
409
517
 
@@ -27,8 +27,8 @@ These rules fire automatically. The AI MUST call the specified tools without ask
27
27
  The founder reviews and approves via email. The AI's job is to draft, send, and chain — not to ask.
28
28
 
29
29
  ### Session Lifecycle
30
- - **Session start**: call \`delimit_ledger_context\` + \`delimit_gov_health\` + \`delimit_inbox_daemon(action="start")\` + \`delimit_notify_routing(action="status")\`
31
- - **Session end or idle**: call \`delimit_session_handoff\` to preserve context for next session
30
+ - **Session start**: call \`delimit_revive\` + \`delimit_ledger_context\` + \`delimit_gov_health\` + \`delimit_inbox_daemon(action="start")\` + \`delimit_notify_routing(action="status")\`
31
+ - **Session end or idle**: call \`delimit_soul_capture\` + \`delimit_session_handoff\` to preserve context for next session
32
32
  - If returning user, summarize what's pending. If new project, call \`delimit_scan\`.
33
33
 
34
34
  ### Code Development (debounced per edit batch)
@@ -82,6 +82,18 @@ The founder reviews and approves via email. The AI's job is to draft, send, and
82
82
  - Max 5 immediate notifications per hour (overflow to digest)
83
83
  - Per-scan fan-out: max 10 targets per notification
84
84
 
85
+ ## Memory Rules (CRITICAL)
86
+
87
+ Routine code changes are auto-captured from git on session exit. You do NOT need to call memory_store for normal work.
88
+
89
+ ONLY use \`memory_store\` to capture:
90
+ - **Failed approaches** — "tried X, it didn't work because Y" (git only shows what survived, not dead ends)
91
+ - **Architecture decisions** — "chose PostgreSQL over Redis for sessions because Z"
92
+ - **Key context** — customer requirements, API constraints, deployment gotchas
93
+ - **Things that will confuse the next session** — non-obvious setups, workarounds, tech debt
94
+
95
+ Do NOT store: routine code changes, file lists, commit messages, status updates — these are in git.
96
+
85
97
  ## What Delimit does:
86
98
  - **API governance** -- lint, diff, semver classification, migration guides
87
99
  - **Persistent context** -- memory and ledger survive across sessions and models
@@ -97,6 +109,29 @@ Add breaking change detection to any repo:
97
109
  spec: api/openapi.yaml
98
110
  \`\`\`
99
111
 
112
+ ## Paying Customers (CRITICAL — Read Before Any Change)
113
+
114
+ Delimit has paying Pro customers. Every code change, MCP tool modification, server update, or API change MUST consider impact on existing users.
115
+
116
+ ### Customer Protection Rules
117
+ - **Before modifying any MCP tool signature** (params, return schema): check if it would break existing Pro users' workflows
118
+ - **Before renaming/removing CLI commands**: these are documented and users depend on them
119
+ - **Before changing license validation**: customers have active license keys (Lemon Squeezy)
120
+ - **Before modifying server.py tool definitions**: Pro users have the MCP server installed locally at ~/.delimit/server/
121
+ - **Before changing JSONL/JSON storage formats**: memory, ledger, evidence files may exist on customer machines
122
+ - **npm publish is a production deploy**: every publish goes to real users, not just us
123
+ - **Gateway → npm sync**: when syncing server.py to the npm bundle, verify no breaking tool changes
124
+ - **Test with \`delimit doctor\`** before any publish to catch config/setup breaks
125
+ - **Backwards compatibility**: new features must not break existing installations. Add, don't remove.
126
+
127
+ ### What Constitutes a Breaking Change for Users
128
+ - MCP tool parameter renamed or removed
129
+ - CLI command renamed or removed
130
+ - Storage format change (memories.jsonl, ledger, evidence, license.json)
131
+ - Python import path changes in server.py
132
+ - Hook format changes in settings.json
133
+ - Default behavior changes (e.g., changing what \`delimit scan\` does with no args)
134
+
100
135
  ## Links
101
136
  - Docs: https://delimit.ai/docs
102
137
  - GitHub: https://github.com/delimit-ai/delimit-mcp-server
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.40",
4
+ "version": "4.1.42",
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": [
package/server.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "url": "https://github.com/delimit-ai/delimit-mcp-server",
8
8
  "source": "github"
9
9
  },
10
- "version": "4.1.38",
10
+ "version": "4.1.40",
11
11
  "websiteUrl": "https://delimit.ai",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "delimit-cli",
16
- "version": "4.1.38",
16
+ "version": "4.1.40",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  }