thumbgate 1.37.1 → 1.37.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 (47) hide show
  1. package/.agents/skills/article-syndication-pipeline/SKILL.md +37 -0
  2. package/.agents/skills/ci-gha-buildkite-patterns-not-clone/SKILL.md +72 -0
  3. package/.agents/skills/cobble-hot-store-compare-not-clone/SKILL.md +80 -0
  4. package/.agents/skills/colab-compute-honesty-not-clone/SKILL.md +70 -0
  5. package/.agents/skills/comet-session-logger/SKILL.md +59 -0
  6. package/.agents/skills/datadog-llm-obs-compare-not-clone/SKILL.md +75 -0
  7. package/.agents/skills/deeppattern-discipline-honesty-not-clone/SKILL.md +73 -0
  8. package/.agents/skills/thumbgate-board-loop/SKILL.md +72 -0
  9. package/.agents/skills/thumbgate-daily-discoveries/SKILL.md +57 -0
  10. package/.agents/skills/token-shunt-honesty-not-clone/SKILL.md +67 -0
  11. package/.agents/skills/typesafe-typed-questions-not-clone/SKILL.md +86 -0
  12. package/.claude-plugin/plugin.json +1 -1
  13. package/.well-known/mcp/server-card.json +1 -1
  14. package/adapters/claude/.mcp.json +2 -2
  15. package/adapters/forge/forge.yaml +3 -3
  16. package/adapters/future-agi/.mcp.json +1 -1
  17. package/adapters/future-agi/config.toml +1 -1
  18. package/adapters/future-agi/opencode.json +1 -1
  19. package/adapters/herdr/herdr-plugin.toml +2 -2
  20. package/adapters/mcp/server-stdio.js +1 -1
  21. package/adapters/opencode/opencode.json +1 -1
  22. package/bin/cli.js +257 -0
  23. package/config/gate-templates.json +96 -0
  24. package/package.json +55 -12
  25. package/public/blog/2026-09-23-datadog-llm-obs-four-practices.html +42 -0
  26. package/public/index.html +2 -2
  27. package/public/install.html +7 -7
  28. package/public/numbers.html +2 -2
  29. package/scripts/ci-gha-buildkite-patterns.js +393 -0
  30. package/scripts/cli-schema.js +90 -0
  31. package/scripts/cobble-hot-store-split.js +600 -0
  32. package/scripts/colab-compute-honesty.js +281 -0
  33. package/scripts/deeppattern-discipline-honesty.js +431 -0
  34. package/scripts/gates-engine.js +16 -5
  35. package/scripts/install-thumbgate-daily-publish-launchd.sh +83 -0
  36. package/scripts/llm-obs-honesty.js +478 -0
  37. package/scripts/thumbgate-board-loop.js +432 -0
  38. package/scripts/thumbgate-daily-discoveries-publish.js +413 -0
  39. package/scripts/thumbgate-daily-discoveries-publish.sh +18 -0
  40. package/scripts/token-shunt-honesty.js +502 -0
  41. package/scripts/typesafe-typed-questions.js +1138 -0
  42. package/server.json +2 -2
  43. package/skills/article-syndication-pipeline/SKILL.md +37 -0
  44. package/skills/comet-session-logger/SKILL.md +59 -0
  45. package/skills/thumbgate-daily-discoveries/SKILL.md +57 -0
  46. package/src/index.js +10 -0
  47. package/src/integrations/ideabrowser-connector.js +190 -0
@@ -0,0 +1,478 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Datadog LLM Observability FORMAT steal — not a product clone.
6
+ *
7
+ * Source: https://lp.datadoghq.com/rs/875-UVY-685/images/eBook-LLMObservabilityBestPractices.pdf
8
+ *
9
+ * Four practices (process only):
10
+ * 1. operational — errors, latency breakdown, token budget alerts
11
+ * 2. security — prompt-injection + PII/secret scrub on traces
12
+ * 3. quality — failure-to-answer, topic relevancy, toxicity, user feedback
13
+ * 4. tracing — end-to-end parented spans for retrieve/llm/tool steps
14
+ *
15
+ * Maps onto existing ThumbGate rails. Does NOT clone Datadog Agent
16
+ * Observability, Sensitive Data Scanner, dd-trace, or OTLP export.
17
+ * Dual-edit wall: PR #3881 Datadog-style engine + PR orchestrator.
18
+ */
19
+
20
+ const fs = require('node:fs');
21
+ const path = require('node:path');
22
+
23
+ const SOURCE_URL = 'https://lp.datadoghq.com/rs/875-UVY-685/images/eBook-LLMObservabilityBestPractices.pdf';
24
+
25
+ const PRACTICES = Object.freeze(['operational', 'security', 'quality', 'tracing']);
26
+
27
+ const PRACTICE_RAILS = Object.freeze({
28
+ operational: {
29
+ datadog: 'Request volume, errors, latency over time; token-consumption alerts; chain latency breakdown',
30
+ rails: [
31
+ 'scripts/latency-budget.js',
32
+ 'scripts/action-receipts.js',
33
+ 'scripts/task-outcomes.js',
34
+ 'config/performance-budgets.json',
35
+ ],
36
+ },
37
+ security: {
38
+ datadog: 'Prompt-injection + toxicity highlights; Sensitive Data Scanner scrubs PII from traces',
39
+ rails: [
40
+ 'scripts/secret-redaction.js',
41
+ 'scripts/secret-scanner.js',
42
+ 'PreToolUse gates / gates-engine.js',
43
+ ],
44
+ },
45
+ quality: {
46
+ datadog: 'Failure to answer, topic relevancy, toxicity, negative sentiment; custom evals from user feedback; topic clusters',
47
+ rails: [
48
+ 'scripts/feedback-quality.js',
49
+ '.claude/scripts/feedback/capture-feedback.js',
50
+ 'scripts/future-agi-evaluator.js',
51
+ ],
52
+ },
53
+ tracing: {
54
+ datadog: 'End-to-end prompt traces; highlight errors/latency; inspect RAG/LLM/tool steps',
55
+ rails: [
56
+ 'scripts/agent-audit-trace.js',
57
+ 'scripts/action-receipts.js',
58
+ ],
59
+ },
60
+ });
61
+
62
+ const DEFAULT_GOLD_TRACE = Object.freeze({
63
+ operational: {
64
+ requestCount: 12,
65
+ errorCount: 1,
66
+ latencyMs: { p50: 120, p95: 380 },
67
+ tokenIn: 2400,
68
+ tokenOut: 600,
69
+ budgetUsd: 10,
70
+ spendUsd: 0.42,
71
+ alerts: ['error_rate', 'latency', 'token_budget'],
72
+ },
73
+ security: {
74
+ promptInjectionChecked: true,
75
+ piiScrubbed: true,
76
+ redactionRail: 'scripts/secret-redaction.js',
77
+ rawPrompt: null,
78
+ },
79
+ quality: {
80
+ failureToAnswer: false,
81
+ topicRelevancy: 'on_topic',
82
+ toxicity: 'clean',
83
+ sentiment: 'neutral',
84
+ userFeedback: 'up',
85
+ },
86
+ tracing: {
87
+ traceId: 'trace-gold',
88
+ spans: [
89
+ { id: 's-in', parentId: null, kind: 'input', latencyMs: 4 },
90
+ { id: 's-rag', parentId: 's-in', kind: 'retrieve', latencyMs: 18, evidenceIds: ['doc-1'] },
91
+ { id: 's-llm', parentId: 's-in', kind: 'llm', latencyMs: 90, tokensIn: 200, tokensOut: 40 },
92
+ { id: 's-tool', parentId: 's-in', kind: 'tool', latencyMs: 25, evidenceIds: ['receipt-1'] },
93
+ ],
94
+ },
95
+ });
96
+
97
+ const CLONE_PATTERNS = Object.freeze([
98
+ { id: 'datadog_class', re: /\bDatadogAgentObservability\b/ },
99
+ { id: 'dd_trace', re: /\b(dd-trace|datadoghq\.com\/api|DD_API_KEY|DD_LLMOBS)\b/i },
100
+ { id: 'clone_sku', re: /\b(clone|install|vendor)\b.{0,40}\bdatadog (agent )?observability\b/i },
101
+ { id: 'otlp_export', re: /\b(OTLP.?export|otelcol|ddog.?llmobs)\b/i },
102
+ ]);
103
+
104
+ const SECRETISH_RE = /sk_live_|sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,}|Bearer\s+[A-Za-z0-9\-._]{20,}/;
105
+
106
+ function normalizeBoolean(value) {
107
+ if (value === true || value === 1) return true;
108
+ if (value === false || value === 0 || value == null) return false;
109
+ return /^(1|true|yes|on)$/i.test(String(value).trim());
110
+ }
111
+
112
+ function detectCloneAttempt(text) {
113
+ const t = String(text || '');
114
+ return CLONE_PATTERNS.filter((p) => p.re.test(t)).map((p) => p.id);
115
+ }
116
+
117
+ function requiredOperational(op = {}) {
118
+ const findings = [];
119
+ const hasCounts = Number.isFinite(Number(op.requestCount)) && Number.isFinite(Number(op.errorCount));
120
+ const lat = op.latencyMs && typeof op.latencyMs === 'object' ? op.latencyMs : {};
121
+ const hasLatency = Number.isFinite(Number(lat.p95)) || Number.isFinite(Number(op.latencyMs));
122
+ const hasTokens = Number.isFinite(Number(op.tokenIn)) || Number.isFinite(Number(op.tokenOut));
123
+ if (!hasCounts || !hasLatency || !hasTokens) {
124
+ findings.push({
125
+ severity: 'fail',
126
+ id: 'missing_operational_metrics',
127
+ message: 'Operational practice needs request/error counts, latency, and token usage. Map onto latency-budget + action-receipts + task-outcomes.',
128
+ });
129
+ }
130
+ const spend = Number(op.spendUsd);
131
+ const budget = Number(op.budgetUsd);
132
+ const alerts = Array.isArray(op.alerts) ? op.alerts.map((a) => String(a).toLowerCase()) : [];
133
+ if (Number.isFinite(spend) && spend > 0 && !Number.isFinite(budget)) {
134
+ findings.push({
135
+ severity: 'fail',
136
+ id: 'missing_token_budget',
137
+ message: 'Token/cost spend is recorded without a budget ceiling. Datadog practice 1 is alert-on-budget, not a dashboard of vibes.',
138
+ });
139
+ }
140
+ if (Number.isFinite(spend) && Number.isFinite(budget) && spend > budget && !alerts.some((a) => /token|budget|cost/.test(a))) {
141
+ findings.push({
142
+ severity: 'fail',
143
+ id: 'missing_token_budget_alert',
144
+ message: 'Spend exceeds budget with no token_budget/cost alert. Wire the tripwire, do not clone Datadog monitors.',
145
+ });
146
+ }
147
+ return findings;
148
+ }
149
+
150
+ function requiredSecurity(sec = {}) {
151
+ const findings = [];
152
+ if (sec.promptInjectionChecked !== true) {
153
+ findings.push({
154
+ severity: 'fail',
155
+ id: 'missing_injection_check',
156
+ message: 'Security practice requires a prompt-injection check on the trace (existing PreToolUse gates), not a Datadog SDS SKU.',
157
+ });
158
+ }
159
+ if (sec.piiScrubbed !== true) {
160
+ findings.push({
161
+ severity: 'fail',
162
+ id: 'missing_pii_scrub',
163
+ message: 'Traces must be scrubbed via scripts/secret-redaction.js before persistence. Do not invent a second scanner.',
164
+ });
165
+ }
166
+ if (sec.rawPrompt && SECRETISH_RE.test(String(sec.rawPrompt))) {
167
+ findings.push({
168
+ severity: 'fail',
169
+ id: 'unredacted_prompt',
170
+ message: 'Raw prompt still contains credential-shaped text. Redact before any lesson/trace write.',
171
+ });
172
+ }
173
+ const rail = String(sec.redactionRail || '');
174
+ if (sec.piiScrubbed === true && rail && !/secret-redaction/.test(rail) && /datadog|sensitive data scanner/i.test(rail)) {
175
+ findings.push({
176
+ severity: 'fail',
177
+ id: 'duplicate_redaction_sku',
178
+ message: 'Redaction must call scripts/secret-redaction.js. A parallel Datadog SDS is a clone.',
179
+ });
180
+ }
181
+ return findings;
182
+ }
183
+
184
+ function requiredQuality(q = {}) {
185
+ const findings = [];
186
+ const hasFailure = typeof q.failureToAnswer === 'boolean';
187
+ const hasTopic = Boolean(q.topicRelevancy);
188
+ const hasTox = Boolean(q.toxicity);
189
+ const hasFeedback = q.userFeedback != null && String(q.userFeedback).trim() !== '';
190
+ if (!hasFailure || !hasTopic || !hasTox) {
191
+ findings.push({
192
+ severity: 'fail',
193
+ id: 'missing_quality_evals',
194
+ message: 'Quality practice needs failure-to-answer, topic relevancy, and toxicity (plus optional sentiment). Map onto feedback-quality / capture-feedback, not Datadog Clusters.',
195
+ });
196
+ }
197
+ if (!hasFeedback) {
198
+ findings.push({
199
+ severity: 'warn',
200
+ id: 'missing_user_feedback_eval',
201
+ message: 'No user feedback on this trace. Custom evals should come from thumbs, not a Datadog cluster view.',
202
+ });
203
+ }
204
+ return findings;
205
+ }
206
+
207
+ function requiredTracing(tr = {}) {
208
+ const findings = [];
209
+ const spans = Array.isArray(tr.spans) ? tr.spans : [];
210
+ if (!tr.traceId || spans.length === 0) {
211
+ findings.push({
212
+ severity: 'fail',
213
+ id: 'missing_trace_spans',
214
+ message: 'Tracing practice needs a traceId and parented spans. Map onto agent-audit-trace.js.',
215
+ });
216
+ return findings;
217
+ }
218
+ const ids = new Set(spans.map((s) => s.id).filter(Boolean));
219
+ const kinds = new Set(spans.map((s) => String(s.kind || '').toLowerCase()));
220
+ if (![...ids].length) {
221
+ findings.push({
222
+ severity: 'fail',
223
+ id: 'missing_span_ids',
224
+ message: 'Every span needs an id so latency bottlenecks can be named.',
225
+ });
226
+ }
227
+ const childMissingParent = spans.some((s) => s.parentId && !ids.has(s.parentId) && s.parentId !== s.id);
228
+ if (childMissingParent) {
229
+ findings.push({
230
+ severity: 'fail',
231
+ id: 'broken_parent_chain',
232
+ message: 'A span parentId does not exist in this trace. End-to-end means the chain is reconstructable.',
233
+ });
234
+ }
235
+ const hasRetrieve = [...kinds].some((k) => /retriev|rag|search/.test(k));
236
+ const hasLlm = [...kinds].some((k) => /llm|model|generate/.test(k));
237
+ if (spans.length >= 2 && !hasLlm) {
238
+ findings.push({
239
+ severity: 'fail',
240
+ id: 'missing_llm_span',
241
+ message: 'Multi-step traces need an llm/model span so token/latency attribution is not a blob.',
242
+ });
243
+ }
244
+ if (hasRetrieve) {
245
+ const rag = spans.filter((s) => /retriev|rag|search/i.test(String(s.kind || '')));
246
+ if (rag.some((s) => !Array.isArray(s.evidenceIds) || s.evidenceIds.length === 0)) {
247
+ findings.push({
248
+ severity: 'fail',
249
+ id: 'retrieve_span_requires_evidence',
250
+ message: 'RAG/retrieve spans must cite evidenceIds (which docs actually landed in the prompt).',
251
+ });
252
+ }
253
+ }
254
+ const tools = spans.filter((s) => /tool/i.test(String(s.kind || '')) || (Array.isArray(s.toolsUsed) && s.toolsUsed.length));
255
+ if (tools.some((s) => !Array.isArray(s.evidenceIds) || s.evidenceIds.length === 0)) {
256
+ findings.push({
257
+ severity: 'fail',
258
+ id: 'tool_span_requires_evidence',
259
+ message: 'Tool spans need evidenceIds (action-receipts). agent-audit-trace already fail-closes this.',
260
+ });
261
+ }
262
+ const missingLatency = spans.filter((s) => !Number.isFinite(Number(s.latencyMs)));
263
+ if (missingLatency.length && spans.length >= 2) {
264
+ findings.push({
265
+ severity: 'warn',
266
+ id: 'missing_span_latency',
267
+ message: 'Some spans lack latencyMs so the chain breakdown cannot name the slow hop.',
268
+ });
269
+ }
270
+ return findings;
271
+ }
272
+
273
+ function auditTrace(trace = {}) {
274
+ const findings = [];
275
+ if (!trace || typeof trace !== 'object' || Array.isArray(trace)) {
276
+ return [{
277
+ severity: 'fail',
278
+ id: 'empty_trace',
279
+ message: 'Trace is missing. A fail-closed LLM-obs audit needs operational, security, quality, and tracing objects.',
280
+ }];
281
+ }
282
+ const hasAny = PRACTICES.some((p) => trace[p] != null);
283
+ if (!hasAny) {
284
+ findings.push({
285
+ severity: 'fail',
286
+ id: 'empty_trace',
287
+ message: 'Trace omits all four practices. Empty {} is not ready.',
288
+ });
289
+ return findings;
290
+ }
291
+ findings.push(...requiredOperational(trace.operational || {}));
292
+ findings.push(...requiredSecurity(trace.security || {}));
293
+ findings.push(...requiredQuality(trace.quality || {}));
294
+ findings.push(...requiredTracing(trace.tracing || {}));
295
+ return findings;
296
+ }
297
+
298
+ function probeRails(rootDir) {
299
+ const root = rootDir || path.resolve(__dirname, '..');
300
+ const checks = [
301
+ { id: 'latency_budget', rel: 'scripts/latency-budget.js' },
302
+ { id: 'action_receipts', rel: 'scripts/action-receipts.js' },
303
+ { id: 'secret_redaction', rel: 'scripts/secret-redaction.js' },
304
+ { id: 'agent_audit_trace', rel: 'scripts/agent-audit-trace.js' },
305
+ { id: 'task_outcomes', rel: 'scripts/task-outcomes.js' },
306
+ ];
307
+ return checks.map((c) => ({
308
+ id: c.id,
309
+ path: c.rel,
310
+ exists: fs.existsSync(path.join(root, c.rel)),
311
+ }));
312
+ }
313
+
314
+ function loadJsonFile(filePath) {
315
+ if (!filePath) return null;
316
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
317
+ }
318
+
319
+ function collectCloneHaystack(options = {}) {
320
+ const parts = [
321
+ options.task,
322
+ options.query,
323
+ options['clone-datadog'] ? 'clone datadog agent observability' : '',
324
+ options.cloneDatadog ? 'clone datadog agent observability DD_API_KEY' : '',
325
+ ];
326
+ if (Array.isArray(options.argv)) parts.push(...options.argv);
327
+ return parts.filter(Boolean).join(' ');
328
+ }
329
+
330
+ function buildLlmObsHonestyReport(options = {}) {
331
+ const root = options.root
332
+ ? path.resolve(String(options.root))
333
+ : path.resolve(__dirname, '..');
334
+ const findings = [];
335
+ const cloneHits = [...new Set(detectCloneAttempt(collectCloneHaystack(options)))];
336
+ if (cloneHits.length) {
337
+ findings.push({
338
+ severity: 'fail',
339
+ id: 'datadog_clone_refused',
340
+ message: `Refusing Datadog Agent Observability / dd-trace / OTLP clone (${cloneHits.join(', ')}). Map the four practices onto existing receipts, redaction, feedback, and audit-trace rails. Do not dual-edit PR #3881.`,
341
+ });
342
+ }
343
+
344
+ const mapOnly = normalizeBoolean(options.map || options['map-only']);
345
+ let trace = null;
346
+ if (!mapOnly) {
347
+ if (options.trace && typeof options.trace === 'object' && !Array.isArray(options.trace)) {
348
+ trace = options.trace;
349
+ } else if (options.trace) {
350
+ trace = loadJsonFile(String(options.trace));
351
+ } else {
352
+ trace = DEFAULT_GOLD_TRACE;
353
+ }
354
+ findings.push(...auditTrace(trace));
355
+ }
356
+
357
+ const probes = probeRails(root);
358
+ for (const missing of probes.filter((p) => !p.exists)) {
359
+ findings.push({
360
+ severity: 'warn',
361
+ id: `missing_rail_${missing.id}`,
362
+ message: `Expected rail missing: ${missing.path}`,
363
+ });
364
+ }
365
+
366
+ let status = 'ready';
367
+ if (findings.some((f) => f.severity === 'fail')) status = 'fail';
368
+ else if (findings.some((f) => f.severity === 'warn')) status = 'ready_with_warnings';
369
+
370
+ const report = {
371
+ name: 'thumbgate-llm-obs-honesty',
372
+ status,
373
+ ok: status !== 'fail',
374
+ practices: PRACTICES.map((id) => ({ id, ...PRACTICE_RAILS[id] })),
375
+ trace: mapOnly ? null : trace,
376
+ railProbes: probes,
377
+ compareNotClone: true,
378
+ siblingWall: 'PR #3881 Datadog-style engine + PR orchestrator — do not dual-edit; orchestrator overlaps board-loop #3883',
379
+ never: [
380
+ 'clone Datadog Agent Observability / Sensitive Data Scanner',
381
+ 'vendor dd-trace or DD_API_KEY LLM obs',
382
+ 'OTLP-export prompt traces',
383
+ 'duplicate secret-redaction.js',
384
+ 'dual-edit PR #3881',
385
+ 'claim Datadog MTTR/token dashboards as ThumbGate measurements',
386
+ ],
387
+ source: SOURCE_URL,
388
+ disclaimer: 'FORMAT steal only. Not affiliated with Datadog. Maps existing ThumbGate receipts/redaction/feedback/audit-trace; does not ship an APM SKU.',
389
+ findings,
390
+ };
391
+ if (mapOnly) report.map = report.practices;
392
+ return report;
393
+ }
394
+
395
+ function formatLlmObsHonestyReport(report) {
396
+ const lines = [
397
+ 'ThumbGate LLM-obs honesty (Datadog FORMAT steal)',
398
+ `Status : ${report.status}`,
399
+ `ok : ${report.ok}`,
400
+ 'Practices:',
401
+ ];
402
+ for (const p of report.practices || []) {
403
+ lines.push(` [${p.id}] ${p.datadog}`);
404
+ lines.push(` rails=${(p.rails || []).join(' · ')}`);
405
+ }
406
+ if (report.findings?.length) {
407
+ lines.push('Findings:');
408
+ for (const f of report.findings) {
409
+ lines.push(` [${f.severity}] ${f.id}: ${f.message}`);
410
+ }
411
+ }
412
+ lines.push(`Never : ${(report.never || []).join('; ')}`);
413
+ lines.push(`Source : ${report.source}`);
414
+ lines.push(report.disclaimer);
415
+ return `${lines.join('\n')}\n`;
416
+ }
417
+
418
+ function printHelp() {
419
+ process.stdout.write(`Usage: node scripts/llm-obs-honesty.js [options]
420
+
421
+ Datadog LLM Observability FORMAT steal — four practices onto existing
422
+ ThumbGate rails. Does not clone Datadog / dd-trace / OTLP.
423
+
424
+ Options:
425
+ --trace=<file.json> Audit an operational/security/quality/tracing payload
426
+ --map-only Print four-practice rail map
427
+ --clone-datadog Fail closed (clone refused)
428
+ --json
429
+ --strict Exit 1 unless status=ready
430
+ --root=<dir>
431
+ `);
432
+ }
433
+
434
+ function parseArgv(argv) {
435
+ const options = { argv };
436
+ for (const arg of argv) {
437
+ if (arg === '--json') options.json = true;
438
+ else if (arg === '--strict') options.strict = true;
439
+ else if (arg === '--map' || arg === '--map-only') options.map = true;
440
+ else if (arg === '--clone-datadog') options['clone-datadog'] = true;
441
+ else if (arg.startsWith('--trace=')) options.trace = arg.slice('--trace='.length);
442
+ else if (arg.startsWith('--root=')) options.root = arg.slice('--root='.length);
443
+ else if (arg.startsWith('--task=')) options.task = arg.slice('--task='.length);
444
+ else if (arg.startsWith('--query=')) options.query = arg.slice('--query='.length);
445
+ }
446
+ return options;
447
+ }
448
+
449
+ function main(argv = process.argv.slice(2)) {
450
+ if (argv.includes('--help') || argv.includes('-h')) {
451
+ printHelp();
452
+ return 0;
453
+ }
454
+ const options = parseArgv(argv);
455
+ const report = buildLlmObsHonestyReport(options);
456
+ if (options.json) process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
457
+ else process.stdout.write(formatLlmObsHonestyReport(report));
458
+ if (options.strict && report.status !== 'ready') return 1;
459
+ if (report.status === 'fail') return 1;
460
+ return 0;
461
+ }
462
+
463
+ module.exports = {
464
+ PRACTICES,
465
+ PRACTICE_RAILS,
466
+ DEFAULT_GOLD_TRACE,
467
+ SOURCE_URL,
468
+ detectCloneAttempt,
469
+ auditTrace,
470
+ probeRails,
471
+ buildLlmObsHonestyReport,
472
+ formatLlmObsHonestyReport,
473
+ main,
474
+ };
475
+
476
+ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(__filename)) {
477
+ process.exitCode = main();
478
+ }