delimit-cli 4.5.1 → 4.5.3

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +15 -5
  3. package/bin/delimit-cli.js +109 -24
  4. package/gateway/ai/content_engine.py +3 -4
  5. package/gateway/ai/inbox_classifier.py +215 -0
  6. package/gateway/ai/integrations/opensage_wrapper.py +4 -1
  7. package/gateway/ai/ledger_manager.py +218 -38
  8. package/gateway/ai/license.py +26 -0
  9. package/gateway/ai/notify.py +68 -3
  10. package/gateway/ai/reddit_proxy.py +93 -15
  11. package/gateway/ai/reddit_scanner.py +36 -18
  12. package/gateway/ai/remote_resolve.py +422 -0
  13. package/gateway/ai/server.py +301 -117
  14. package/gateway/ai/social_capability/__init__.py +6 -0
  15. package/gateway/ai/social_capability/capability_validator.py +367 -0
  16. package/gateway/ai/social_capability/current_capabilities.yaml +95 -0
  17. package/gateway/ai/social_capability/fit_floor.py +360 -0
  18. package/gateway/ai/social_queue.py +307 -0
  19. package/gateway/ai/supabase_sync.py +14 -2
  20. package/gateway/ai/swarm.py +29 -11
  21. package/gateway/ai/tui.py +6 -2
  22. package/gateway/ai/vendor_news/__init__.py +14 -0
  23. package/gateway/ai/vendor_news/drafter.py +562 -0
  24. package/gateway/ai/vendor_news/sensor.py +509 -0
  25. package/gateway/ai/vendor_news/watchlist.yaml +71 -0
  26. package/gateway/ai/x_ranker.py +417 -0
  27. package/lib/attest-mcp.js +487 -0
  28. package/lib/attest-telemetry.js +48 -0
  29. package/lib/delimit-home.js +35 -0
  30. package/lib/delimit-template.js +14 -0
  31. package/package.json +25 -3
  32. package/scripts/postinstall.js +89 -40
  33. package/adapters/codex-security.js +0 -64
  34. package/adapters/codex-skill.js +0 -78
  35. package/gateway/ai/content_grounding/__init__.py +0 -98
  36. package/gateway/ai/content_grounding/build.py +0 -350
  37. package/gateway/ai/content_grounding/consume.py +0 -280
  38. package/gateway/ai/content_grounding/features.py +0 -218
  39. package/gateway/ai/content_grounding/fixtures/fail/01_missing_evidence.json +0 -9
  40. package/gateway/ai/content_grounding/fixtures/fail/02_unknown_evidence_prefix.json +0 -9
  41. package/gateway/ai/content_grounding/fixtures/fail/03_banned_comparative.json +0 -17
  42. package/gateway/ai/content_grounding/fixtures/fail/04_banned_adoption.json +0 -17
  43. package/gateway/ai/content_grounding/fixtures/fail/05_aggregate_no_numeric.json +0 -17
  44. package/gateway/ai/content_grounding/fixtures/fail/06_unversioned_inference_rule.json +0 -18
  45. package/gateway/ai/content_grounding/fixtures/pass/01_feature_shipped.json +0 -18
  46. package/gateway/ai/content_grounding/fixtures/pass/02_aggregate_claim.json +0 -23
  47. package/gateway/ai/content_grounding/fixtures/pass/03_attestation.json +0 -16
  48. package/gateway/ai/content_grounding/schemas/claim.schema.json +0 -40
  49. package/gateway/ai/content_grounding/schemas/event.schema.json +0 -23
  50. package/gateway/ai/content_grounding/schemas.py +0 -276
  51. package/gateway/ai/content_grounding/telemetry.py +0 -221
  52. package/gateway/ai/inbox_drafts/__init__.py +0 -61
  53. package/gateway/ai/inbox_drafts/registry.py +0 -412
  54. package/gateway/ai/inbox_drafts/schema.py +0 -374
  55. package/gateway/ai/inbox_executor.py +0 -565
@@ -0,0 +1,487 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * STR-656 — `delimit attest mcp` local-preview implementation.
5
+ *
6
+ * Runs the five deterministic checks defined by the MCP Attestation
7
+ * Methodology v1 (delimit.ai/methodology/mcp-attestation) and prints a
8
+ * PREVIEW REPORT. This module deliberately does NOT sign anything, does
9
+ * NOT publish to delimit.ai, does NOT generate a badge.
10
+ *
11
+ * The methodology gate (STR-657) keeps the public signed-attestation
12
+ * surface locked until: 30d methodology visibility + 14d CLI shipped +
13
+ * 5+ merge-gate pilot reference accounts + incident-response process
14
+ * documented. Until that gate exits, this module emits previews only.
15
+ *
16
+ * Each check returns a result object:
17
+ * { id, status: 'pass' | 'fail' | 'skip' | 'error', detail, evidence }
18
+ * - `status` drives the preview report's pass/fail headline.
19
+ * - `detail` is human-readable (one line for the table).
20
+ * - `evidence` is structured data captured for the would-be attestation.
21
+ *
22
+ * Re-runnability: every check operates on the resolved repo at HEAD (or
23
+ * a caller-provided commit SHA). The check inputs are deterministic so
24
+ * the output bytes for a given commit should be byte-stable across
25
+ * machines, modulo the timestamp.
26
+ */
27
+
28
+ const fs = require('fs');
29
+ const path = require('path');
30
+ const { execSync, spawn } = require('child_process');
31
+
32
+ const METHODOLOGY_URL = 'https://delimit.ai/methodology/mcp-attestation';
33
+ const METHODOLOGY_VERSION = 'v1';
34
+
35
+ function _resolveCommit(repoDir) {
36
+ try {
37
+ return execSync('git rev-parse HEAD', {
38
+ cwd: repoDir, encoding: 'utf-8', timeout: 3000,
39
+ }).trim();
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ function _readJsonSafe(p) {
46
+ try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }
47
+ }
48
+
49
+ // ────────────────────────────────────────────────────────────────────
50
+ // Check 1: dependency-security
51
+ // ────────────────────────────────────────────────────────────────────
52
+ function checkDependencySecurity(repoDir) {
53
+ const id = 'dependency-security';
54
+ const pkgPath = path.join(repoDir, 'package.json');
55
+ if (!fs.existsSync(pkgPath)) {
56
+ return {
57
+ id, status: 'skip',
58
+ detail: 'no package.json — non-Node project (Python/Go/Rust attest paths land in v2)',
59
+ evidence: { reason: 'package_json_absent' },
60
+ };
61
+ }
62
+ let auditResult;
63
+ try {
64
+ const out = execSync('npm audit --json --omit=dev', {
65
+ cwd: repoDir, encoding: 'utf-8', timeout: 30000,
66
+ stdio: ['ignore', 'pipe', 'ignore'],
67
+ });
68
+ auditResult = JSON.parse(out);
69
+ } catch (e) {
70
+ // npm audit exits non-zero when vulns found — output is on stdout.
71
+ try { auditResult = JSON.parse(e.stdout); } catch { auditResult = null; }
72
+ }
73
+ if (!auditResult || !auditResult.metadata) {
74
+ return {
75
+ id, status: 'error',
76
+ detail: 'npm audit unavailable (no lockfile or registry unreachable)',
77
+ evidence: { reason: 'npm_audit_failed' },
78
+ };
79
+ }
80
+ const vulns = auditResult.metadata.vulnerabilities || {};
81
+ const critical = vulns.critical || 0;
82
+ const high = vulns.high || 0;
83
+ const status = critical > 0 ? 'fail' : 'pass';
84
+ return {
85
+ id, status,
86
+ detail: `npm audit: ${critical} critical, ${high} high, ${vulns.moderate || 0} moderate, ${vulns.low || 0} low`,
87
+ evidence: {
88
+ counts: vulns,
89
+ audit_source: 'npm-audit',
90
+ },
91
+ };
92
+ }
93
+
94
+ // ────────────────────────────────────────────────────────────────────
95
+ // Check 2: sbom-presence
96
+ // ────────────────────────────────────────────────────────────────────
97
+ function checkSbomPresence(repoDir) {
98
+ const id = 'sbom-presence';
99
+ const candidates = [
100
+ 'sbom.json', 'sbom.cdx.json', 'sbom.spdx.json',
101
+ '.sbom/cyclonedx.json', '.sbom/spdx.json',
102
+ 'cyclonedx.json', 'spdx.json',
103
+ ];
104
+ for (const rel of candidates) {
105
+ const p = path.join(repoDir, rel);
106
+ if (fs.existsSync(p)) {
107
+ const parsed = _readJsonSafe(p);
108
+ if (!parsed) {
109
+ return {
110
+ id, status: 'fail',
111
+ detail: `${rel} present but unparseable JSON`,
112
+ evidence: { path: rel, parseable: false },
113
+ };
114
+ }
115
+ const format =
116
+ parsed.bomFormat ? 'CycloneDX' :
117
+ parsed.spdxVersion ? 'SPDX' :
118
+ 'unknown';
119
+ return {
120
+ id, status: 'pass',
121
+ detail: `${rel} (${format})`,
122
+ evidence: { path: rel, format, parseable: true },
123
+ };
124
+ }
125
+ }
126
+ return {
127
+ id, status: 'skip',
128
+ detail: 'no SBOM file found — see methodology for accepted paths',
129
+ evidence: { searched: candidates },
130
+ };
131
+ }
132
+
133
+ // ────────────────────────────────────────────────────────────────────
134
+ // Check 3: signed-tag
135
+ // ────────────────────────────────────────────────────────────────────
136
+ function checkSignedTag(repoDir) {
137
+ const id = 'signed-tag';
138
+ let tag;
139
+ try {
140
+ tag = execSync('git describe --tags --exact-match HEAD', {
141
+ cwd: repoDir, encoding: 'utf-8', timeout: 3000,
142
+ stdio: ['ignore', 'pipe', 'ignore'],
143
+ }).trim();
144
+ } catch {
145
+ return {
146
+ id, status: 'skip',
147
+ detail: 'HEAD is not on a tagged commit (signed-tag check is release-only)',
148
+ evidence: { reason: 'no_exact_tag' },
149
+ };
150
+ }
151
+ let verifyOut;
152
+ try {
153
+ verifyOut = execSync(`git verify-tag --raw ${tag}`, {
154
+ cwd: repoDir, encoding: 'utf-8', timeout: 5000,
155
+ stdio: ['ignore', 'pipe', 'pipe'],
156
+ });
157
+ } catch {
158
+ return {
159
+ id, status: 'skip',
160
+ detail: `tag ${tag} present but unsigned (sigstore/git-tag signature absent — not a failure, just recorded)`,
161
+ evidence: { tag, signed: false },
162
+ };
163
+ }
164
+ return {
165
+ id, status: 'pass',
166
+ detail: `tag ${tag} signed`,
167
+ evidence: { tag, signed: true, verify_output: verifyOut.trim().slice(0, 200) },
168
+ };
169
+ }
170
+
171
+ // ────────────────────────────────────────────────────────────────────
172
+ // Check 4: mcp-protocol-conformance (live probe — STR-656 Q1)
173
+ // ────────────────────────────────────────────────────────────────────
174
+ //
175
+ // The probe boots the server's declared entry point in a child process,
176
+ // completes the MCP initialize handshake, and calls tools/list over
177
+ // stdio. The captured tool signatures are evidence input for any future
178
+ // signed attestation; signature drift across attested versions is the
179
+ // next-attestation comparison input.
180
+ //
181
+ // Sandbox posture (best-effort, not a hard isolation boundary): empty
182
+ // HOME, restricted PATH, no extra env, hard timeout, SIGKILL on cleanup.
183
+ // The local preview is for surfacing shape; the hosted attestation will
184
+ // run inside a stronger sandbox.
185
+ //
186
+ function _findMcpEntry(pkg) {
187
+ // Priority order:
188
+ // 1. pkg.mcp.command (free-form shell string — explicit opt-in)
189
+ // 2. pkg.mcp.entry (path to a node script)
190
+ // 3. pkg.bin (string) → node <pkg.bin>
191
+ // 4. pkg.bin (object) → node <first value>
192
+ // 5. pkg.main → node <pkg.main>
193
+ if (pkg.mcp && typeof pkg.mcp.command === 'string') {
194
+ return { kind: 'shell', value: pkg.mcp.command };
195
+ }
196
+ if (pkg.mcp && typeof pkg.mcp.entry === 'string') {
197
+ return { kind: 'node', value: pkg.mcp.entry };
198
+ }
199
+ if (typeof pkg.bin === 'string') {
200
+ return { kind: 'node', value: pkg.bin };
201
+ }
202
+ if (pkg.bin && typeof pkg.bin === 'object') {
203
+ const first = Object.values(pkg.bin)[0];
204
+ if (typeof first === 'string') return { kind: 'node', value: first };
205
+ }
206
+ if (typeof pkg.main === 'string') {
207
+ return { kind: 'node', value: pkg.main };
208
+ }
209
+ return null;
210
+ }
211
+
212
+ function _probeMcpServer(repoDir, entry, timeoutMs) {
213
+ return new Promise((resolve) => {
214
+ const cmd = entry.kind === 'shell' ? '/bin/sh' : process.execPath;
215
+ const args = entry.kind === 'shell' ? ['-c', entry.value] : [entry.value];
216
+ let proc;
217
+ try {
218
+ proc = spawn(cmd, args, {
219
+ cwd: repoDir,
220
+ stdio: ['pipe', 'pipe', 'pipe'],
221
+ env: {
222
+ PATH: process.env.PATH || '/usr/local/bin:/usr/bin:/bin',
223
+ NODE_NO_WARNINGS: '1',
224
+ DELIMIT_ATTEST_PROBE: '1',
225
+ },
226
+ });
227
+ } catch (e) {
228
+ return resolve({ ok: false, reason: 'spawn_failed', error: e.message });
229
+ }
230
+ const stdoutBuf = [];
231
+ const stderrBuf = [];
232
+ let resolved = false;
233
+ const finish = (result) => {
234
+ if (resolved) return;
235
+ resolved = true;
236
+ try { proc.kill('SIGTERM'); } catch {}
237
+ setTimeout(() => { try { proc.kill('SIGKILL'); } catch {} }, 500).unref();
238
+ resolve(result);
239
+ };
240
+ const timer = setTimeout(() => {
241
+ finish({
242
+ ok: false, reason: 'timeout',
243
+ stderr_tail: stderrBuf.join('').slice(-500),
244
+ });
245
+ }, timeoutMs);
246
+ timer.unref();
247
+ proc.stdout.on('data', (chunk) => {
248
+ stdoutBuf.push(chunk.toString());
249
+ const combined = stdoutBuf.join('');
250
+ for (const line of combined.split('\n')) {
251
+ if (!line.trim()) continue;
252
+ try {
253
+ const msg = JSON.parse(line);
254
+ if (msg.id === 2 && msg.result && Array.isArray(msg.result.tools)) {
255
+ clearTimeout(timer);
256
+ return finish({ ok: true, tools: msg.result.tools });
257
+ }
258
+ if (msg.id === 2 && msg.error) {
259
+ clearTimeout(timer);
260
+ return finish({ ok: false, reason: 'tools_list_error', mcp_error: msg.error });
261
+ }
262
+ } catch { /* incomplete line, keep buffering */ }
263
+ }
264
+ });
265
+ proc.stderr.on('data', (chunk) => { stderrBuf.push(chunk.toString()); });
266
+ proc.on('error', (e) => {
267
+ clearTimeout(timer);
268
+ finish({ ok: false, reason: 'process_error', error: e.message });
269
+ });
270
+ proc.on('exit', (code) => {
271
+ clearTimeout(timer);
272
+ finish({
273
+ ok: false, reason: 'process_exit', code,
274
+ stderr_tail: stderrBuf.join('').slice(-500),
275
+ });
276
+ });
277
+ const init = JSON.stringify({
278
+ jsonrpc: '2.0', id: 1, method: 'initialize',
279
+ params: {
280
+ protocolVersion: '2024-11-05',
281
+ capabilities: {},
282
+ clientInfo: { name: 'delimit-attest', version: '1.0' },
283
+ },
284
+ }) + '\n';
285
+ const initialized = JSON.stringify({
286
+ jsonrpc: '2.0', method: 'notifications/initialized', params: {},
287
+ }) + '\n';
288
+ const listTools = JSON.stringify({
289
+ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {},
290
+ }) + '\n';
291
+ try {
292
+ proc.stdin.write(init);
293
+ proc.stdin.write(initialized);
294
+ proc.stdin.write(listTools);
295
+ } catch (e) {
296
+ clearTimeout(timer);
297
+ finish({ ok: false, reason: 'stdin_write_failed', error: e.message });
298
+ }
299
+ });
300
+ }
301
+
302
+ async function checkMcpProtocolConformance(repoDir) {
303
+ const id = 'mcp-protocol-conformance';
304
+ const pkgPath = path.join(repoDir, 'package.json');
305
+ const pkg = _readJsonSafe(pkgPath);
306
+ if (!pkg) {
307
+ return {
308
+ id, status: 'skip',
309
+ detail: 'no package.json to enumerate MCP entry point',
310
+ evidence: { reason: 'package_json_absent' },
311
+ };
312
+ }
313
+ const hasMcpDep =
314
+ (pkg.dependencies && Object.keys(pkg.dependencies).some(k => k.startsWith('@modelcontextprotocol'))) ||
315
+ (pkg.devDependencies && Object.keys(pkg.devDependencies).some(k => k.startsWith('@modelcontextprotocol')));
316
+ if (!hasMcpDep && !pkg.mcp) {
317
+ return {
318
+ id, status: 'skip',
319
+ detail: 'no @modelcontextprotocol/* dep or mcp config block — not an MCP server',
320
+ evidence: { reason: 'mcp_dependency_absent' },
321
+ };
322
+ }
323
+ const entry = _findMcpEntry(pkg);
324
+ if (!entry) {
325
+ return {
326
+ id, status: 'skip',
327
+ detail: 'MCP entry point not declared (set pkg.mcp.command, pkg.mcp.entry, or pkg.bin)',
328
+ evidence: { reason: 'no_entry_point', mcp_dep_detected: hasMcpDep },
329
+ };
330
+ }
331
+ if (entry.kind === 'node') {
332
+ const abs = path.join(repoDir, entry.value);
333
+ if (!fs.existsSync(abs)) {
334
+ return {
335
+ id, status: 'error',
336
+ detail: `entry point not found on disk: ${entry.value}`,
337
+ evidence: { entry, missing: true },
338
+ };
339
+ }
340
+ }
341
+ const probe = await _probeMcpServer(repoDir, entry, 8000);
342
+ if (!probe.ok) {
343
+ return {
344
+ id, status: 'error',
345
+ detail: `live probe failed: ${probe.reason}${probe.error ? ' — ' + probe.error : ''}`,
346
+ evidence: { entry, probe },
347
+ };
348
+ }
349
+ const toolSignatures = probe.tools.map(t => ({
350
+ name: t.name,
351
+ description_present: !!t.description,
352
+ input_schema_keys: t.inputSchema && t.inputSchema.properties
353
+ ? Object.keys(t.inputSchema.properties).sort()
354
+ : [],
355
+ }));
356
+ return {
357
+ id, status: 'pass',
358
+ detail: `live probe: ${probe.tools.length} tools enumerated via tools/list`,
359
+ evidence: {
360
+ entry,
361
+ tool_count: probe.tools.length,
362
+ tool_signatures: toolSignatures,
363
+ note: 'Signature drift across attested versions is the next-attestation comparison input.',
364
+ },
365
+ };
366
+ }
367
+
368
+ // ────────────────────────────────────────────────────────────────────
369
+ // Check 5: known-cve
370
+ // ────────────────────────────────────────────────────────────────────
371
+ function checkKnownCve(repoDir) {
372
+ const id = 'known-cve';
373
+ // npm audit (Check 1) already cross-references GHSA + npm advisory DB,
374
+ // which together cover most public CVEs for the npm ecosystem. For
375
+ // v1 the known-cve check piggy-backs on npm audit's advisory feed and
376
+ // records the advisory IDs — full CVE-database resolution is staged
377
+ // for the v1.1 release once we have a deterministic offline mirror.
378
+ const depResult = checkDependencySecurity(repoDir);
379
+ if (depResult.status === 'skip' || depResult.status === 'error') {
380
+ return {
381
+ id, status: 'skip',
382
+ detail: `cve check requires dependency-security to run (was: ${depResult.status})`,
383
+ evidence: { upstream: depResult.id },
384
+ };
385
+ }
386
+ const counts = depResult.evidence.counts || {};
387
+ const total = (counts.critical || 0) + (counts.high || 0) +
388
+ (counts.moderate || 0) + (counts.low || 0);
389
+ const status = (counts.critical || 0) > 0 ? 'fail' : 'pass';
390
+ return {
391
+ id, status,
392
+ detail: `${total} advisory IDs cross-referenced (CVE-database probe lands in v1.1)`,
393
+ evidence: { advisory_counts: counts, source: 'npm-audit/GHSA' },
394
+ };
395
+ }
396
+
397
+ // ────────────────────────────────────────────────────────────────────
398
+ // Top-level runner
399
+ // ────────────────────────────────────────────────────────────────────
400
+
401
+ async function _safeRun(id, fn, repoDir) {
402
+ // Per-check runtime guard (STR-656 Q6 panel verdict). A single check
403
+ // throwing — sync or async — must not crash the runner. It must
404
+ // surface as an `error` status so the report is still complete and
405
+ // the exit-code tier logic can treat it as a tool error (exit 2),
406
+ // not a policy fail.
407
+ try {
408
+ const result = fn(repoDir);
409
+ return await Promise.resolve(result);
410
+ } catch (e) {
411
+ return {
412
+ id,
413
+ status: 'error',
414
+ detail: `check threw: ${e && e.message ? e.message : 'unknown error'}`,
415
+ evidence: { reason: 'check_exception', message: e && e.message },
416
+ };
417
+ }
418
+ }
419
+
420
+ async function runAttestMcp(opts = {}) {
421
+ const repoDir = path.resolve(opts.path || process.cwd());
422
+ if (!fs.existsSync(repoDir)) {
423
+ return { error: `path not found: ${repoDir}` };
424
+ }
425
+ const commit = _resolveCommit(repoDir);
426
+ const checks = [
427
+ await _safeRun('dependency-security', checkDependencySecurity, repoDir),
428
+ await _safeRun('sbom-presence', checkSbomPresence, repoDir),
429
+ await _safeRun('signed-tag', checkSignedTag, repoDir),
430
+ await _safeRun('mcp-protocol-conformance', checkMcpProtocolConformance, repoDir),
431
+ await _safeRun('known-cve', checkKnownCve, repoDir),
432
+ ];
433
+ return {
434
+ kind: 'mcp_attestation_preview',
435
+ methodology_version: METHODOLOGY_VERSION,
436
+ methodology_url: METHODOLOGY_URL,
437
+ repo: { path: repoDir, commit },
438
+ checks,
439
+ timestamp: new Date().toISOString(),
440
+ signed: false, // STR-657 gate: never signed in scaffold.
441
+ public: false, // STR-657 gate: never published.
442
+ scaffold_notice:
443
+ 'DELIMIT ATTESTATION PREVIEW — NOT A PUBLIC ATTESTATION. ' +
444
+ 'MCP attestation is one surface of the Delimit merge gate product family ' +
445
+ '(PR review + release attestations ship under the same delimit-cli). ' +
446
+ 'The public signed-attestation surface is gated on the methodology being live ≥30d, ' +
447
+ 'this CLI being shipped ≥14d, and 5+ merge-gate pilot reference accounts. ' +
448
+ `See ${METHODOLOGY_URL}.`,
449
+ };
450
+ }
451
+
452
+ function _statusEmoji(s) {
453
+ return { pass: 'PASS', fail: 'FAIL', skip: 'SKIP', error: 'ERR ' }[s] || s;
454
+ }
455
+
456
+ function renderPreview(report) {
457
+ const lines = [];
458
+ lines.push('');
459
+ lines.push(' DELIMIT ATTESTATION PREVIEW — NOT A PUBLIC ATTESTATION');
460
+ lines.push('');
461
+ lines.push(` Methodology: ${report.methodology_url} (${report.methodology_version})`);
462
+ lines.push(` Repo: ${report.repo.path}`);
463
+ if (report.repo.commit) {
464
+ lines.push(` Commit: ${report.repo.commit}`);
465
+ }
466
+ lines.push(` Timestamp: ${report.timestamp}`);
467
+ lines.push('');
468
+ lines.push(' Checks:');
469
+ for (const c of report.checks) {
470
+ lines.push(` [${_statusEmoji(c.status)}] ${c.id.padEnd(30)} ${c.detail || ''}`);
471
+ }
472
+ lines.push('');
473
+ lines.push(` Signed: ${report.signed ? 'yes' : 'no (preview)'}`);
474
+ lines.push(` Public: ${report.public ? 'yes' : 'no (gate locked)'}`);
475
+ lines.push('');
476
+ lines.push(' This is a local preview only. To understand exactly what this');
477
+ lines.push(` attestation would and would NOT cover, read ${report.methodology_url}.`);
478
+ lines.push('');
479
+ return lines.join('\n');
480
+ }
481
+
482
+ module.exports = {
483
+ runAttestMcp,
484
+ renderPreview,
485
+ METHODOLOGY_URL,
486
+ METHODOLOGY_VERSION,
487
+ };
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * STR-656 Q5 — minimal telemetry counter for `delimit attest <kind>`.
5
+ *
6
+ * What this records (one JSONL line per invocation):
7
+ * timestamp, kind, outcome, methodology_version, check_summary
8
+ *
9
+ * What this does NOT record:
10
+ * - repo path, commit SHA, file contents, dependency names, tool names
11
+ * - any user-identifying data
12
+ *
13
+ * Records land in ~/.delimit/telemetry/attest-<kind>.jsonl. They are local
14
+ * by default — this module does NOT phone home. The path is documented so
15
+ * a future opt-in upload can read the same file without changing the CLI.
16
+ *
17
+ * Kill switch: `DELIMIT_NO_TELEMETRY=1` disables all writes. Honored across
18
+ * the Delimit CLI; this module is one of several call sites.
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const os = require('os');
23
+ const path = require('path');
24
+
25
+ const TELEMETRY_DIR = path.join(os.homedir(), '.delimit', 'telemetry');
26
+
27
+ function _isDisabled() {
28
+ const v = process.env.DELIMIT_NO_TELEMETRY;
29
+ return v === '1' || v === 'true' || v === 'yes';
30
+ }
31
+
32
+ function recordTelemetry(record) {
33
+ if (_isDisabled()) return;
34
+ const line = {
35
+ ts: new Date().toISOString(),
36
+ ...record,
37
+ };
38
+ try {
39
+ fs.mkdirSync(TELEMETRY_DIR, { recursive: true });
40
+ const file = path.join(TELEMETRY_DIR, `attest-${record.kind || 'unknown'}.jsonl`);
41
+ fs.appendFileSync(file, JSON.stringify(line) + '\n');
42
+ } catch {
43
+ // Telemetry must never affect the user's exit code or output.
44
+ // Swallow all errors (EROFS, EACCES, ENOSPC, etc.) silently.
45
+ }
46
+ }
47
+
48
+ module.exports = { recordTelemetry, TELEMETRY_DIR };
@@ -0,0 +1,35 @@
1
+ // lib/delimit-home.js
2
+ //
3
+ // LED-1188: single source of truth for resolving the Delimit private-state
4
+ // directory (~/.delimit by default). Replaces ~37 hardcoded sites across
5
+ // bin/delimit-setup.js, bin/delimit-cli.js, and gateway adapters.
6
+ //
7
+ // Resolution order:
8
+ // 1. $DELIMIT_HOME (preferred — explicit, easy to reason about)
9
+ // 2. $DELIMIT_NAMESPACE_ROOT (gateway-compat — see continuity.py:454)
10
+ // 3. <homedir>/.delimit (default)
11
+ //
12
+ // USAGE
13
+ // const { delimitHome, homeSubpath } = require('../lib/delimit-home');
14
+ // const ledger = path.join(delimitHome(), 'ledger');
15
+ // const ledger = homeSubpath('ledger'); // shorthand
16
+ //
17
+ // Both helpers re-resolve on every call so tests can mutate process.env
18
+ // between calls without module-cache invalidation.
19
+
20
+ const os = require('os');
21
+ const path = require('path');
22
+
23
+ function delimitHome() {
24
+ const fromEnv = process.env.DELIMIT_HOME || process.env.DELIMIT_NAMESPACE_ROOT;
25
+ if (fromEnv && fromEnv.trim()) {
26
+ return fromEnv;
27
+ }
28
+ return path.join(os.homedir(), '.delimit');
29
+ }
30
+
31
+ function homeSubpath(...segments) {
32
+ return path.join(delimitHome(), ...segments);
33
+ }
34
+
35
+ module.exports = { delimitHome, homeSubpath };
@@ -32,6 +32,20 @@ Unify all AI coding assistants with persistent context, governance, and multi-mo
32
32
 
33
33
  These rules fire automatically. Call the listed tools without asking permission.
34
34
 
35
+ ### Operating model — default to subagent dispatch (revised 2026-05-01)
36
+
37
+ Non-trivial engineering work routes through in-session subagents, not in-thread execution by the orchestrator.
38
+
39
+ - **Engineering work** with **≥3 independent parallelizable sub-tasks** OR a sub-task with **isolated write scope and amortizable parallel effort** (own subsystem, own artifact boundary, own tests) → spawn an engineering subagent (Agent tool, subagent_type=engineering) for the parallel arms; the orchestrator does the sequential glue and integration. Do NOT do the parallel work in-thread.
40
+ - **Foundational decisions** (pricing, naming, public-facing copy framing, doctrine edits, layer-separation calls) → call \`delimit_deliberate\` BEFORE applying. This extends the existing external-PR rule to internal doctrine edits.
41
+ - **Sensing / outreach research** → \`delimit_sense\` or \`delimit_intel_query\` or \`delimit_sensor_github_issue\`, not manual web searches.
42
+ - **Social drafts** → \`delimit_social_generate\` then \`delimit_social_post\`, never hand-drafted (per existing rule).
43
+ - **Audit trail for dispatched work** → \`delimit_agent_dispatch\` records intent + assignee + outcome for replay. It is a **planning + audit surface**, not a queue processor; tasks dispatched here do NOT auto-execute. Real autonomous queue execution is a future capability with strict sandboxing + founder-approval semantics.
44
+
45
+ **Bypass is allowed** when the task is genuinely conversational, sub-5-minute and undecomposable, the founder explicitly directs in-session execution, or no automation/agent surface exists for the task yet. State the bypass reason in one line; founder override is always honored. No per-task approval required for stated bypasses.
46
+
47
+ **Reversibility**: 30-day review (2026-05-30). Dispatch decisions and bypasses are logged via \`delimit_ledger_add\` for the audit trail. If the rule produces over-dispatch friction or under-dispatch (continued in-thread default), it is adjusted or retired at the review.
48
+
35
49
  ### Session Lifecycle
36
50
  - **Session start**: call \`delimit_revive\` + \`delimit_ledger_context\` + \`delimit_gov_health\`
37
51
  - **Session end or idle**: call \`delimit_soul_capture\` + \`delimit_session_handoff\`
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.5.1",
4
+ "version": "4.5.3",
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": [
@@ -16,6 +16,9 @@
16
16
  "!gateway/ai/founding_users.py",
17
17
  "!gateway/ai/inbox_daemon.py",
18
18
  "!gateway/ai/inbox_daemon_runner.py",
19
+ "!gateway/ai/self_repair/",
20
+ "!gateway/ai/self_repair_daemon.py",
21
+ "!gateway/ai/corp_dashboard.py",
19
22
  "!gateway/ai/deliberation.py",
20
23
  "!gateway/ai/dv_mention_tracker.py",
21
24
  "!gateway/ai/sensor_twttr.py",
@@ -26,6 +29,9 @@
26
29
  "!gateway/ai/content_intel.py",
27
30
  "!gateway/ai/loop_daemon.py",
28
31
  "!gateway/ai/loop_engine.py",
32
+ "!gateway/ai/content_grounding/",
33
+ "!gateway/ai/inbox_drafts/",
34
+ "!gateway/ai/inbox_executor.py",
29
35
  "scripts/",
30
36
  "!scripts/crosspost_devto.py",
31
37
  "!scripts/repo_targeting.py",
@@ -48,7 +54,7 @@
48
54
  "postinstall": "node scripts/postinstall.js",
49
55
  "sync-gateway": "bash scripts/sync-gateway.sh",
50
56
  "prepublishOnly": "bash scripts/publish-ci-guard.sh && npm run sync-gateway && bash scripts/security-check.sh",
51
- "test": "node --test tests/setup-onboarding.test.js tests/setup-matrix.test.js tests/setup-no-clobber.test.js tests/config-export-import.test.js tests/cross-model-hooks.test.js tests/golden-path.test.js tests/v420-features.test.js tests/v43-wrap-engine.test.js tests/v43-trust-page-engine.test.js tests/v43-ai-sbom-engine.test.js"
57
+ "test": "node --test tests/setup-onboarding.test.js tests/setup-matrix.test.js tests/setup-no-clobber.test.js tests/config-export-import.test.js tests/cross-model-hooks.test.js tests/golden-path.test.js tests/v420-features.test.js tests/v43-wrap-engine.test.js tests/v43-trust-page-engine.test.js tests/v43-ai-sbom-engine.test.js tests/attest-mcp.test.js tests/delimit-home.test.js tests/postinstall-hardening.test.js"
52
58
  },
53
59
  "keywords": [
54
60
  "openapi",
@@ -79,7 +85,23 @@
79
85
  "gemini-cli",
80
86
  "cursor",
81
87
  "ai-governance",
82
- "ai-agents"
88
+ "ai-agents",
89
+ "ai-code-review",
90
+ "ci-governance",
91
+ "merge-gate",
92
+ "attestation",
93
+ "signed-attestation",
94
+ "sigstore",
95
+ "sbom",
96
+ "supply-chain-security",
97
+ "json-schema",
98
+ "json-schema-diff",
99
+ "policy-as-code",
100
+ "audit-trail",
101
+ "ai-coding-assistant",
102
+ "pull-request",
103
+ "pr-comment",
104
+ "developer-tools"
83
105
  ],
84
106
  "author": "Delimit AI <hello@delimit.ai>",
85
107
  "license": "MIT",