@fulmenhq/tsfulmen 0.3.0 → 0.3.2

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 (42) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/config/crucible-ts/repository/app-identity/app-identity.example.yaml +8 -0
  3. package/config/crucible-ts/repository/app-identity/fixtures/valid/typescript-package.yaml +17 -0
  4. package/config/crucible-ts/repository/app-identity/parity-snapshot.json +28 -0
  5. package/config/crucible-ts/taxonomy/metrics.yaml +1 -1
  6. package/dist/appidentity/index.js +156 -4150
  7. package/dist/appidentity/index.js.map +1 -1
  8. package/dist/bin/prometheus-cli.d.ts +1 -0
  9. package/dist/bin/prometheus-cli.js +9331 -0
  10. package/dist/bin/prometheus-cli.js.map +1 -0
  11. package/dist/bin/schema-cli.d.ts +1 -0
  12. package/dist/bin/schema-cli.js +6104 -0
  13. package/dist/bin/schema-cli.js.map +1 -0
  14. package/dist/bin/signals-cli.d.ts +1 -0
  15. package/dist/bin/signals-cli.js +2104 -0
  16. package/dist/bin/signals-cli.js.map +1 -0
  17. package/dist/config/index.js +29 -4915
  18. package/dist/config/index.js.map +1 -1
  19. package/dist/crucible/index.js +120 -5336
  20. package/dist/crucible/index.js.map +1 -1
  21. package/dist/errors/index.js +52 -4434
  22. package/dist/errors/index.js.map +1 -1
  23. package/dist/foundry/index.js +197 -1874
  24. package/dist/foundry/index.js.map +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js +161 -4154
  27. package/dist/index.js.map +1 -1
  28. package/dist/pathfinder/index.js +47 -4378
  29. package/dist/pathfinder/index.js.map +1 -1
  30. package/dist/reports/license-inventory.csv +2 -2
  31. package/dist/schema/index.js +0 -4
  32. package/dist/schema/index.js.map +1 -1
  33. package/dist/signals/index.js +231 -3570
  34. package/dist/signals/index.js.map +1 -1
  35. package/dist/telemetry/http/index.js +94 -5280
  36. package/dist/telemetry/http/index.js.map +1 -1
  37. package/dist/telemetry/index.js +70 -4786
  38. package/dist/telemetry/index.js.map +1 -1
  39. package/dist/telemetry/prometheus/index.js +461 -4450
  40. package/dist/telemetry/prometheus/index.js.map +1 -1
  41. package/package.json +8 -2
  42. package/schemas/crucible-ts/config/repository/app-identity/v1.0.0/app-identity.schema.json +34 -0
@@ -0,0 +1,2104 @@
1
+ #!/usr/bin/env node
2
+ import { readFile, access } from 'fs/promises';
3
+ import { dirname, join, relative, extname } from 'path';
4
+ import { parse } from 'yaml';
5
+ import addFormats from 'ajv-formats';
6
+ import 'child_process';
7
+ import { fileURLToPath } from 'url';
8
+ import glob from 'fast-glob';
9
+ import { Command } from 'commander';
10
+ import Ajv from 'ajv';
11
+ import Ajv2019 from 'ajv/dist/2019.js';
12
+ import Ajv2020 from 'ajv/dist/2020.js';
13
+ import AjvDraft04 from 'ajv-draft-04';
14
+
15
+ var __defProp = Object.defineProperty;
16
+ var __getOwnPropNames = Object.getOwnPropertyNames;
17
+ var __esm = (fn, res) => function __init() {
18
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, { get: all[name], enumerable: true });
23
+ };
24
+
25
+ // src/crucible/foundry/exitCodes.ts
26
+ var exitCodes;
27
+ var init_exitCodes = __esm({
28
+ "src/crucible/foundry/exitCodes.ts"() {
29
+ exitCodes = {
30
+ // Standard Exit Codes (0-1)
31
+ // POSIX standard success and generic failure codes
32
+ EXIT_SUCCESS: 0,
33
+ EXIT_FAILURE: 1,
34
+ // Networking & Port Management (10-19)
35
+ // Network-related failures (ports, connectivity, etc.)
36
+ EXIT_PORT_IN_USE: 10,
37
+ EXIT_PORT_RANGE_EXHAUSTED: 11,
38
+ EXIT_INSTANCE_ALREADY_RUNNING: 12,
39
+ EXIT_NETWORK_UNREACHABLE: 13,
40
+ EXIT_CONNECTION_REFUSED: 14,
41
+ EXIT_CONNECTION_TIMEOUT: 15,
42
+ // Configuration & Validation (20-29)
43
+ // Configuration errors, validation failures, version mismatches
44
+ EXIT_CONFIG_INVALID: 20,
45
+ EXIT_MISSING_DEPENDENCY: 21,
46
+ EXIT_SSOT_VERSION_MISMATCH: 22,
47
+ EXIT_CONFIG_FILE_NOT_FOUND: 23,
48
+ EXIT_ENVIRONMENT_INVALID: 24,
49
+ // Runtime Errors (30-39)
50
+ // Errors during normal operation (health checks, database, etc.)
51
+ EXIT_HEALTH_CHECK_FAILED: 30,
52
+ EXIT_DATABASE_UNAVAILABLE: 31,
53
+ EXIT_EXTERNAL_SERVICE_UNAVAILABLE: 32,
54
+ EXIT_RESOURCE_EXHAUSTED: 33,
55
+ EXIT_OPERATION_TIMEOUT: 34,
56
+ // Command-Line Usage Errors (40-49)
57
+ // Invalid arguments, missing required flags, usage errors
58
+ EXIT_INVALID_ARGUMENT: 40,
59
+ EXIT_MISSING_REQUIRED_ARGUMENT: 41,
60
+ EXIT_USAGE: 64,
61
+ // Permissions & File Access (50-59)
62
+ // Permission denied, file not found, access errors
63
+ EXIT_PERMISSION_DENIED: 50,
64
+ EXIT_FILE_NOT_FOUND: 51,
65
+ EXIT_DIRECTORY_NOT_FOUND: 52,
66
+ EXIT_FILE_READ_ERROR: 53,
67
+ EXIT_FILE_WRITE_ERROR: 54,
68
+ // Data & Processing Errors (60-69)
69
+ // Data validation, parsing, transformation failures
70
+ EXIT_DATA_INVALID: 60,
71
+ EXIT_PARSE_ERROR: 61,
72
+ EXIT_TRANSFORMATION_FAILED: 62,
73
+ EXIT_DATA_CORRUPT: 63,
74
+ // Security & Authentication (70-79)
75
+ // Authentication failures, authorization errors, security violations
76
+ EXIT_AUTHENTICATION_FAILED: 70,
77
+ EXIT_AUTHORIZATION_FAILED: 71,
78
+ EXIT_SECURITY_VIOLATION: 72,
79
+ EXIT_CERTIFICATE_INVALID: 73,
80
+ // Observability & Monitoring (80-89)
81
+ // Observability infrastructure failures. Use when observability is CRITICAL to operation (e.g., monitoring agents, telemetry exporters). For workhorses where observability is auxiliary: - Log warning and continue (don't exit) - Emit degraded health status - Only exit if explicitly configured (fail_on_observability_error: true)
82
+ EXIT_METRICS_UNAVAILABLE: 80,
83
+ EXIT_TRACING_FAILED: 81,
84
+ EXIT_LOGGING_FAILED: 82,
85
+ EXIT_ALERT_SYSTEM_FAILED: 83,
86
+ EXIT_STRUCTURED_LOGGING_FAILED: 84,
87
+ // Testing & Validation (91-99)
88
+ // Test execution outcomes and validation failures. NOTE: Test harnesses MUST use EXIT_SUCCESS (0) for successful test runs per ecosystem conventions (pytest, Go testing, Jest all use 0 for success). Codes in this category are for FAILURES and exceptional conditions only.
89
+ EXIT_TEST_FAILURE: 91,
90
+ EXIT_TEST_ERROR: 92,
91
+ EXIT_TEST_INTERRUPTED: 93,
92
+ EXIT_TEST_USAGE_ERROR: 94,
93
+ EXIT_TEST_NO_TESTS_COLLECTED: 95,
94
+ EXIT_COVERAGE_THRESHOLD_NOT_MET: 96,
95
+ // Shell & Process Control (124-127)
96
+ // Exit codes from shell conventions and process control utilities (timeout, exec)
97
+ EXIT_TIMEOUT: 124,
98
+ EXIT_TIMEOUT_INTERNAL: 125,
99
+ EXIT_CANNOT_EXECUTE: 126,
100
+ EXIT_NOT_FOUND: 127,
101
+ // Signal-Induced Exits (128-165)
102
+ // Process terminated by Unix signals (128+N pattern per POSIX). Signal codes follow Linux numbering; macOS/FreeBSD differ for SIGUSR1/SIGUSR2. For full signal semantics, see config/library/foundry/signals.yaml. For signal handling patterns, see docs/standards/library/modules/signal-handling.md.
103
+ EXIT_SIGNAL_HUP: 129,
104
+ EXIT_SIGNAL_INT: 130,
105
+ EXIT_SIGNAL_QUIT: 131,
106
+ EXIT_SIGNAL_KILL: 137,
107
+ EXIT_SIGNAL_PIPE: 141,
108
+ EXIT_SIGNAL_ALRM: 142,
109
+ EXIT_SIGNAL_TERM: 143,
110
+ EXIT_SIGNAL_USR1: 138,
111
+ EXIT_SIGNAL_USR2: 140
112
+ };
113
+ }
114
+ });
115
+
116
+ // src/foundry/exit-codes/capabilities.ts
117
+ var init_capabilities = __esm({
118
+ "src/foundry/exit-codes/capabilities.ts"() {
119
+ }
120
+ });
121
+
122
+ // src/foundry/exit-codes/simplified.ts
123
+ var init_simplified = __esm({
124
+ "src/foundry/exit-codes/simplified.ts"() {
125
+ init_exitCodes();
126
+ }
127
+ });
128
+
129
+ // src/foundry/exit-codes/index.ts
130
+ var init_exit_codes = __esm({
131
+ "src/foundry/exit-codes/index.ts"() {
132
+ init_exitCodes();
133
+ init_capabilities();
134
+ init_simplified();
135
+ }
136
+ });
137
+
138
+ // src/telemetry/counter.ts
139
+ var Counter;
140
+ var init_counter = __esm({
141
+ "src/telemetry/counter.ts"() {
142
+ Counter = class {
143
+ constructor(name) {
144
+ this.name = name;
145
+ }
146
+ value = 0;
147
+ labeledValues = /* @__PURE__ */ new Map();
148
+ /**
149
+ * Increment counter by delta (default: 1)
150
+ *
151
+ * @param delta - Amount to increment (must be non-negative)
152
+ * @param labels - Optional label dimensions for this observation
153
+ * @throws {Error} If delta is negative
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * counter.inc(); // Increment unlabeled by 1
158
+ * counter.inc(5); // Increment unlabeled by 5
159
+ * counter.inc(1, { status: '200' }); // Increment labeled instance
160
+ * counter.inc(1, { result: 'success' }); // Different label set
161
+ * ```
162
+ */
163
+ inc(delta = 1, labels) {
164
+ if (delta < 0) {
165
+ throw new Error(`Counter delta must be non-negative, got: ${delta}`);
166
+ }
167
+ if (labels && Object.keys(labels).length > 0) {
168
+ const labelKey = this.serializeLabels(labels);
169
+ const current = this.labeledValues.get(labelKey) || 0;
170
+ this.labeledValues.set(labelKey, current + delta);
171
+ } else {
172
+ this.value += delta;
173
+ }
174
+ }
175
+ /**
176
+ * Get current counter value (unlabeled)
177
+ */
178
+ getValue() {
179
+ return this.value;
180
+ }
181
+ /**
182
+ * Get all labeled values
183
+ * @returns Map of serialized label keys to values
184
+ */
185
+ getLabeledValues() {
186
+ return new Map(this.labeledValues);
187
+ }
188
+ /**
189
+ * Get value for specific label combination
190
+ */
191
+ getValueForLabels(labels) {
192
+ const labelKey = this.serializeLabels(labels);
193
+ return this.labeledValues.get(labelKey) || 0;
194
+ }
195
+ /**
196
+ * Reset counter to zero (all label combinations)
197
+ */
198
+ reset() {
199
+ this.value = 0;
200
+ this.labeledValues.clear();
201
+ }
202
+ /**
203
+ * Serialize labels to deterministic string key
204
+ * Format: key1=value1,key2=value2 (sorted by key)
205
+ */
206
+ serializeLabels(labels) {
207
+ return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
208
+ }
209
+ };
210
+ }
211
+ });
212
+
213
+ // src/telemetry/gauge.ts
214
+ var Gauge;
215
+ var init_gauge = __esm({
216
+ "src/telemetry/gauge.ts"() {
217
+ Gauge = class {
218
+ constructor(name) {
219
+ this.name = name;
220
+ }
221
+ value = 0;
222
+ labeledValues = /* @__PURE__ */ new Map();
223
+ /**
224
+ * Set gauge to specific value
225
+ *
226
+ * @param value - New gauge value (can be any number, including negative)
227
+ * @param labels - Optional label dimensions for this observation
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * gauge.set(42); // Set unlabeled to 42
232
+ * gauge.set(-10); // Negative values allowed
233
+ * gauge.set(1, { phase: 'collect' }); // Set labeled instance
234
+ * ```
235
+ */
236
+ set(value, labels) {
237
+ if (labels && Object.keys(labels).length > 0) {
238
+ const labelKey = this.serializeLabels(labels);
239
+ this.labeledValues.set(labelKey, value);
240
+ } else {
241
+ this.value = value;
242
+ }
243
+ }
244
+ /**
245
+ * Increment gauge by delta (default: 1)
246
+ *
247
+ * @param delta - Amount to increment (can be negative)
248
+ * @param labels - Optional label dimensions for this observation
249
+ */
250
+ inc(delta = 1, labels) {
251
+ if (labels && Object.keys(labels).length > 0) {
252
+ const labelKey = this.serializeLabels(labels);
253
+ const current = this.labeledValues.get(labelKey) || 0;
254
+ this.labeledValues.set(labelKey, current + delta);
255
+ } else {
256
+ this.value += delta;
257
+ }
258
+ }
259
+ /**
260
+ * Decrement gauge by delta (default: 1)
261
+ *
262
+ * @param delta - Amount to decrement (can be negative)
263
+ * @param labels - Optional label dimensions for this observation
264
+ */
265
+ dec(delta = 1, labels) {
266
+ if (labels && Object.keys(labels).length > 0) {
267
+ const labelKey = this.serializeLabels(labels);
268
+ const current = this.labeledValues.get(labelKey) || 0;
269
+ this.labeledValues.set(labelKey, current - delta);
270
+ } else {
271
+ this.value -= delta;
272
+ }
273
+ }
274
+ /**
275
+ * Get current gauge value (unlabeled)
276
+ */
277
+ getValue() {
278
+ return this.value;
279
+ }
280
+ /**
281
+ * Get all labeled values
282
+ * @returns Map of serialized label keys to values
283
+ */
284
+ getLabeledValues() {
285
+ return new Map(this.labeledValues);
286
+ }
287
+ /**
288
+ * Get value for specific label combination
289
+ */
290
+ getValueForLabels(labels) {
291
+ const labelKey = this.serializeLabels(labels);
292
+ return this.labeledValues.get(labelKey) || 0;
293
+ }
294
+ /**
295
+ * Reset gauge to zero (all label combinations)
296
+ */
297
+ reset() {
298
+ this.value = 0;
299
+ this.labeledValues.clear();
300
+ }
301
+ /**
302
+ * Serialize labels to deterministic string key
303
+ * Format: key1=value1,key2=value2 (sorted by key)
304
+ */
305
+ serializeLabels(labels) {
306
+ return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
307
+ }
308
+ };
309
+ }
310
+ });
311
+ async function getDefaultUnit(name) {
312
+ return TaxonomyLoader.getInstance().getDefaultUnit(name);
313
+ }
314
+ var DEFAULT_MS_BUCKETS, TaxonomyLoader;
315
+ var init_taxonomy = __esm({
316
+ "src/telemetry/taxonomy.ts"() {
317
+ DEFAULT_MS_BUCKETS = [1, 5, 10, 50, 100, 500, 1e3, 5e3, 1e4];
318
+ TaxonomyLoader = class _TaxonomyLoader {
319
+ static instance;
320
+ taxonomy = null;
321
+ loadPromise = null;
322
+ loadError = null;
323
+ constructor() {
324
+ }
325
+ /**
326
+ * Get singleton instance
327
+ */
328
+ static getInstance() {
329
+ if (!_TaxonomyLoader.instance) {
330
+ _TaxonomyLoader.instance = new _TaxonomyLoader();
331
+ }
332
+ return _TaxonomyLoader.instance;
333
+ }
334
+ /**
335
+ * Load taxonomy from YAML file
336
+ */
337
+ async load() {
338
+ if (this.taxonomy !== null) {
339
+ return this.taxonomy;
340
+ }
341
+ if (this.loadError !== null) {
342
+ throw this.loadError;
343
+ }
344
+ if (this.loadPromise) {
345
+ return this.loadPromise;
346
+ }
347
+ this.loadPromise = (async () => {
348
+ try {
349
+ const taxonomyPath = join(
350
+ __dirname,
351
+ "..",
352
+ "..",
353
+ "config",
354
+ "crucible-ts",
355
+ "taxonomy",
356
+ "metrics.yaml"
357
+ );
358
+ const content = await readFile(taxonomyPath, "utf-8");
359
+ this.taxonomy = parse(content);
360
+ return this.taxonomy;
361
+ } catch (err) {
362
+ this.loadError = err instanceof Error ? err : new Error(String(err));
363
+ throw new Error(`Failed to load metrics taxonomy: ${this.loadError.message}`);
364
+ }
365
+ })();
366
+ return this.loadPromise;
367
+ }
368
+ /**
369
+ * Get taxonomy (async)
370
+ */
371
+ async getTaxonomy() {
372
+ return this.load();
373
+ }
374
+ /**
375
+ * Get metric definition by name
376
+ */
377
+ async getMetric(name) {
378
+ const taxonomy = await this.load();
379
+ return taxonomy.metrics.find((m) => m.name === name);
380
+ }
381
+ /**
382
+ * Get default unit for metric
383
+ */
384
+ async getDefaultUnit(name) {
385
+ const metric = await this.getMetric(name);
386
+ return metric?.unit;
387
+ }
388
+ /**
389
+ * Get default histogram buckets for metric
390
+ * Returns ADR-0007 buckets for _ms metrics, undefined for others
391
+ */
392
+ async getDefaultBuckets(name) {
393
+ if (name.endsWith("_ms")) {
394
+ const taxonomy = await this.load();
395
+ return taxonomy.defaults.histogram_buckets.ms_metrics;
396
+ }
397
+ return void 0;
398
+ }
399
+ /**
400
+ * Check if metric name is valid (exists in taxonomy)
401
+ */
402
+ async isValidMetricName(name) {
403
+ try {
404
+ const taxonomy = await this.load();
405
+ return taxonomy.metrics.some((m) => m.name === name);
406
+ } catch {
407
+ return false;
408
+ }
409
+ }
410
+ /**
411
+ * Reset loader state (for testing)
412
+ * @internal
413
+ */
414
+ static _reset() {
415
+ _TaxonomyLoader.instance = new _TaxonomyLoader();
416
+ }
417
+ };
418
+ }
419
+ });
420
+
421
+ // src/telemetry/histogram.ts
422
+ var Histogram;
423
+ var init_histogram = __esm({
424
+ "src/telemetry/histogram.ts"() {
425
+ init_taxonomy();
426
+ Histogram = class {
427
+ constructor(name, options) {
428
+ this.name = name;
429
+ if (options?.buckets) {
430
+ this.buckets = [...options.buckets].sort((a, b) => a - b);
431
+ } else if (name.endsWith("_ms") || name.endsWith("_seconds")) {
432
+ this.buckets = [...DEFAULT_MS_BUCKETS];
433
+ } else {
434
+ this.buckets = [];
435
+ }
436
+ for (const bucket of this.buckets) {
437
+ this.bucketCounts.set(bucket, 0);
438
+ }
439
+ }
440
+ count = 0;
441
+ sum = 0;
442
+ bucketCounts = /* @__PURE__ */ new Map();
443
+ labeledStates = /* @__PURE__ */ new Map();
444
+ buckets;
445
+ /**
446
+ * Record an observation
447
+ *
448
+ * @param value - Value to observe (typically a duration in ms or seconds)
449
+ * @param labels - Optional label dimensions for this observation
450
+ *
451
+ * @example
452
+ * ```typescript
453
+ * const start = performance.now();
454
+ * // ... operation ...
455
+ * histogram.observe(performance.now() - start);
456
+ * histogram.observe(duration, { phase: 'collect', result: 'success' });
457
+ * ```
458
+ */
459
+ observe(value, labels) {
460
+ if (labels && Object.keys(labels).length > 0) {
461
+ const labelKey = this.serializeLabels(labels);
462
+ let state = this.labeledStates.get(labelKey);
463
+ if (!state) {
464
+ state = {
465
+ count: 0,
466
+ sum: 0,
467
+ bucketCounts: /* @__PURE__ */ new Map()
468
+ };
469
+ for (const bucket of this.buckets) {
470
+ state.bucketCounts.set(bucket, 0);
471
+ }
472
+ this.labeledStates.set(labelKey, state);
473
+ }
474
+ state.count++;
475
+ state.sum += value;
476
+ for (const bucket of this.buckets) {
477
+ if (value <= bucket) {
478
+ state.bucketCounts.set(bucket, (state.bucketCounts.get(bucket) || 0) + 1);
479
+ }
480
+ }
481
+ } else {
482
+ this.count++;
483
+ this.sum += value;
484
+ for (const bucket of this.buckets) {
485
+ if (value <= bucket) {
486
+ this.bucketCounts.set(bucket, (this.bucketCounts.get(bucket) || 0) + 1);
487
+ }
488
+ }
489
+ }
490
+ }
491
+ /**
492
+ * Get histogram summary
493
+ *
494
+ * Returns OTLP-compatible histogram summary with cumulative bucket counts.
495
+ */
496
+ getSummary() {
497
+ const buckets = this.buckets.map((le) => ({
498
+ le,
499
+ count: this.bucketCounts.get(le) || 0
500
+ }));
501
+ return {
502
+ count: this.count,
503
+ sum: this.sum,
504
+ buckets
505
+ };
506
+ }
507
+ /**
508
+ * Get current observation count
509
+ */
510
+ getCount() {
511
+ return this.count;
512
+ }
513
+ /**
514
+ * Get sum of all observed values
515
+ */
516
+ getSum() {
517
+ return this.sum;
518
+ }
519
+ /**
520
+ * Get average of observed values
521
+ */
522
+ getAverage() {
523
+ return this.count > 0 ? this.sum / this.count : 0;
524
+ }
525
+ /**
526
+ * Get all labeled summaries
527
+ * @returns Map of serialized label keys to histogram summaries
528
+ */
529
+ getLabeledSummaries() {
530
+ const summaries = /* @__PURE__ */ new Map();
531
+ for (const [labelKey, state] of this.labeledStates) {
532
+ const buckets = this.buckets.map((le) => ({
533
+ le,
534
+ count: state.bucketCounts.get(le) || 0
535
+ }));
536
+ summaries.set(labelKey, {
537
+ count: state.count,
538
+ sum: state.sum,
539
+ buckets
540
+ });
541
+ }
542
+ return summaries;
543
+ }
544
+ /**
545
+ * Get summary for specific label combination
546
+ */
547
+ getSummaryForLabels(labels) {
548
+ const labelKey = this.serializeLabels(labels);
549
+ const state = this.labeledStates.get(labelKey);
550
+ if (!state) {
551
+ return null;
552
+ }
553
+ const buckets = this.buckets.map((le) => ({
554
+ le,
555
+ count: state.bucketCounts.get(le) || 0
556
+ }));
557
+ return {
558
+ count: state.count,
559
+ sum: state.sum,
560
+ buckets
561
+ };
562
+ }
563
+ /**
564
+ * Reset histogram to initial state (all label combinations)
565
+ */
566
+ reset() {
567
+ this.count = 0;
568
+ this.sum = 0;
569
+ for (const bucket of this.buckets) {
570
+ this.bucketCounts.set(bucket, 0);
571
+ }
572
+ this.labeledStates.clear();
573
+ }
574
+ /**
575
+ * Serialize labels to deterministic string key
576
+ * Format: key1=value1,key2=value2 (sorted by key)
577
+ */
578
+ serializeLabels(labels) {
579
+ return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
580
+ }
581
+ };
582
+ }
583
+ });
584
+
585
+ // src/telemetry/registry.ts
586
+ var MetricsRegistry;
587
+ var init_registry = __esm({
588
+ "src/telemetry/registry.ts"() {
589
+ init_counter();
590
+ init_gauge();
591
+ init_histogram();
592
+ init_taxonomy();
593
+ MetricsRegistry = class {
594
+ counters = /* @__PURE__ */ new Map();
595
+ gauges = /* @__PURE__ */ new Map();
596
+ histograms = /* @__PURE__ */ new Map();
597
+ /**
598
+ * Get or create a counter
599
+ *
600
+ * @param name - Metric name from taxonomy
601
+ * @returns Counter instance
602
+ *
603
+ * @example
604
+ * ```typescript
605
+ * const counter = registry.counter('schema_validations');
606
+ * counter.inc();
607
+ * ```
608
+ */
609
+ counter(name) {
610
+ let counter = this.counters.get(name);
611
+ if (!counter) {
612
+ counter = new Counter(name);
613
+ this.counters.set(name, counter);
614
+ }
615
+ return counter;
616
+ }
617
+ /**
618
+ * Get or create a gauge
619
+ *
620
+ * @param name - Metric name from taxonomy
621
+ * @returns Gauge instance
622
+ *
623
+ * @example
624
+ * ```typescript
625
+ * const gauge = registry.gauge('foundry_lookup_count');
626
+ * gauge.set(42);
627
+ * ```
628
+ */
629
+ gauge(name) {
630
+ let gauge = this.gauges.get(name);
631
+ if (!gauge) {
632
+ gauge = new Gauge(name);
633
+ this.gauges.set(name, gauge);
634
+ }
635
+ return gauge;
636
+ }
637
+ /**
638
+ * Get or create a histogram
639
+ *
640
+ * @param name - Metric name from taxonomy
641
+ * @param options - Optional histogram options
642
+ * @returns Histogram instance
643
+ *
644
+ * @example
645
+ * ```typescript
646
+ * // Auto-applies ADR-0007 buckets for _ms metrics
647
+ * const histogram = registry.histogram('config_load_ms');
648
+ * histogram.observe(42.5);
649
+ *
650
+ * // Custom buckets
651
+ * const custom = registry.histogram('custom_metric', {
652
+ * buckets: [10, 50, 100, 500, 1000]
653
+ * });
654
+ * ```
655
+ */
656
+ histogram(name, options) {
657
+ let histogram = this.histograms.get(name);
658
+ if (!histogram) {
659
+ histogram = new Histogram(name, options);
660
+ this.histograms.set(name, histogram);
661
+ }
662
+ return histogram;
663
+ }
664
+ /**
665
+ * Export all metrics as events
666
+ *
667
+ * Returns array of schema-compliant MetricsEvent objects.
668
+ * Does not clear metrics (use flush() to clear after export).
669
+ *
670
+ * @returns Promise resolving to array of metrics events
671
+ *
672
+ * @example
673
+ * ```typescript
674
+ * const events = await registry.export();
675
+ * console.log(JSON.stringify(events, null, 2));
676
+ * ```
677
+ */
678
+ async export() {
679
+ const events = [];
680
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
681
+ for (const [name, counter] of this.counters) {
682
+ const unit = await getDefaultUnit(name);
683
+ events.push({
684
+ timestamp,
685
+ name,
686
+ value: counter.getValue(),
687
+ unit
688
+ });
689
+ for (const [labelKey, value] of counter.getLabeledValues()) {
690
+ if (value > 0) {
691
+ const tags = this.deserializeLabels(labelKey);
692
+ events.push({
693
+ timestamp,
694
+ name,
695
+ value,
696
+ tags,
697
+ unit
698
+ });
699
+ }
700
+ }
701
+ }
702
+ for (const [name, gauge] of this.gauges) {
703
+ const unit = await getDefaultUnit(name);
704
+ events.push({
705
+ timestamp,
706
+ name,
707
+ value: gauge.getValue(),
708
+ unit
709
+ });
710
+ for (const [labelKey, value] of gauge.getLabeledValues()) {
711
+ const tags = this.deserializeLabels(labelKey);
712
+ events.push({
713
+ timestamp,
714
+ name,
715
+ value,
716
+ tags,
717
+ unit
718
+ });
719
+ }
720
+ }
721
+ for (const [name, histogram] of this.histograms) {
722
+ const unit = await getDefaultUnit(name);
723
+ events.push({
724
+ timestamp,
725
+ name,
726
+ value: histogram.getSummary(),
727
+ unit
728
+ });
729
+ for (const [labelKey, summary] of histogram.getLabeledSummaries()) {
730
+ if (summary.count > 0) {
731
+ const tags = this.deserializeLabels(labelKey);
732
+ events.push({
733
+ timestamp,
734
+ name,
735
+ value: summary,
736
+ tags,
737
+ unit
738
+ });
739
+ }
740
+ }
741
+ }
742
+ return events;
743
+ }
744
+ /**
745
+ * Deserialize label key back to tags object
746
+ * Format: key1=value1,key2=value2 → {key1: "value1", key2: "value2"}
747
+ */
748
+ deserializeLabels(labelKey) {
749
+ if (!labelKey) {
750
+ return {};
751
+ }
752
+ const tags = {};
753
+ for (const pair of labelKey.split(",")) {
754
+ const [key, value] = pair.split("=");
755
+ if (key && value) {
756
+ tags[key] = value;
757
+ }
758
+ }
759
+ return tags;
760
+ }
761
+ /**
762
+ * Export and clear all metrics
763
+ *
764
+ * Exports metrics as events, optionally emits them via logger,
765
+ * then resets all metrics to zero.
766
+ *
767
+ * @param options - Flush options
768
+ * @returns Promise resolving to array of exported events
769
+ *
770
+ * @example
771
+ * ```typescript
772
+ * // Export and clear
773
+ * const events = await registry.flush();
774
+ *
775
+ * // Export, emit to logger, and clear
776
+ * const events = await registry.flush({
777
+ * emit: (events) => console.log(JSON.stringify(events))
778
+ * });
779
+ * ```
780
+ */
781
+ async flush(options) {
782
+ const events = await this.export();
783
+ try {
784
+ if (options?.emit) {
785
+ options.emit(events);
786
+ }
787
+ } finally {
788
+ this.clear();
789
+ }
790
+ return events;
791
+ }
792
+ /**
793
+ * Clear all metrics (reset to zero)
794
+ *
795
+ * Resets all counters, gauges, and histograms to their initial state.
796
+ */
797
+ clear() {
798
+ for (const counter of this.counters.values()) {
799
+ counter.reset();
800
+ }
801
+ for (const gauge of this.gauges.values()) {
802
+ gauge.reset();
803
+ }
804
+ for (const histogram of this.histograms.values()) {
805
+ histogram.reset();
806
+ }
807
+ }
808
+ /**
809
+ * Get all registered metric names
810
+ *
811
+ * Returns array of all metric names that have been accessed
812
+ * (counters, gauges, or histograms).
813
+ */
814
+ getMetricNames() {
815
+ const names = /* @__PURE__ */ new Set();
816
+ for (const name of this.counters.keys()) {
817
+ names.add(name);
818
+ }
819
+ for (const name of this.gauges.keys()) {
820
+ names.add(name);
821
+ }
822
+ for (const name of this.histograms.keys()) {
823
+ names.add(name);
824
+ }
825
+ return Array.from(names);
826
+ }
827
+ /**
828
+ * Get total count of registered metrics
829
+ */
830
+ getMetricCount() {
831
+ return this.counters.size + this.gauges.size + this.histograms.size;
832
+ }
833
+ };
834
+ }
835
+ });
836
+
837
+ // src/telemetry/types.ts
838
+ var init_types = __esm({
839
+ "src/telemetry/types.ts"() {
840
+ }
841
+ });
842
+ function applyFulmenAjvFormats(ajv, options = {}) {
843
+ const mode = options.mode ?? "fast";
844
+ const formats = options.formats ?? DEFAULT_FORMATS;
845
+ addFormats(ajv, { mode, formats });
846
+ return ajv;
847
+ }
848
+ var DEFAULT_FORMATS;
849
+ var init_ajv_formats = __esm({
850
+ "src/schema/ajv-formats.ts"() {
851
+ DEFAULT_FORMATS = [
852
+ "date-time",
853
+ "email",
854
+ "hostname",
855
+ "ipv4",
856
+ "ipv6",
857
+ "uri",
858
+ "uri-reference",
859
+ "uuid"
860
+ ];
861
+ }
862
+ });
863
+
864
+ // src/schema/errors.ts
865
+ var SchemaValidationError;
866
+ var init_errors = __esm({
867
+ "src/schema/errors.ts"() {
868
+ SchemaValidationError = class _SchemaValidationError extends Error {
869
+ constructor(message, schemaId, diagnostics = [], source, cause) {
870
+ super(message);
871
+ this.schemaId = schemaId;
872
+ this.diagnostics = diagnostics;
873
+ this.source = source;
874
+ this.cause = cause;
875
+ this.name = "SchemaValidationError";
876
+ if (Error.captureStackTrace) {
877
+ Error.captureStackTrace(this, _SchemaValidationError);
878
+ }
879
+ }
880
+ /**
881
+ * Create error for schema not found
882
+ */
883
+ static schemaNotFound(schemaId) {
884
+ return new _SchemaValidationError(`Schema not found: ${schemaId}`, schemaId);
885
+ }
886
+ /**
887
+ * Create error for invalid schema input
888
+ */
889
+ static invalidSchemaInput(source, details) {
890
+ return new _SchemaValidationError(`Invalid schema input: ${details}`, void 0, [], source);
891
+ }
892
+ /**
893
+ * Create error for validation failure
894
+ */
895
+ static validationFailed(schemaId, diagnostics, source) {
896
+ const errorCount = diagnostics.filter((d) => d.severity === "ERROR").length;
897
+ const warningCount = diagnostics.filter((d) => d.severity === "WARN").length;
898
+ const message = `Schema validation failed: ${errorCount} error(s), ${warningCount} warning(s)`;
899
+ return new _SchemaValidationError(message, schemaId, diagnostics, source);
900
+ }
901
+ /**
902
+ * Create error for goneat binary not found
903
+ */
904
+ static goneatNotFound(goneatPath) {
905
+ const pathInfo = goneatPath ? ` at ${goneatPath}` : "";
906
+ return new _SchemaValidationError(
907
+ `Goneat binary not found${pathInfo}. Falling back to AJV validation.`
908
+ );
909
+ }
910
+ /**
911
+ * Create error for goneat execution failure
912
+ */
913
+ static goneatExecutionFailed(error) {
914
+ return new _SchemaValidationError(
915
+ "Goneat execution failed. Falling back to AJV validation.",
916
+ void 0,
917
+ [],
918
+ void 0,
919
+ error
920
+ );
921
+ }
922
+ /**
923
+ * Create error for empty schema input
924
+ */
925
+ static emptySchemaInput(source) {
926
+ return new _SchemaValidationError("Schema content is empty", void 0, [], source);
927
+ }
928
+ /**
929
+ * Create error for parse failure
930
+ */
931
+ static parseFailed(source, error) {
932
+ return new _SchemaValidationError(
933
+ `Failed to parse schema: ${error.message}`,
934
+ void 0,
935
+ [],
936
+ source,
937
+ error
938
+ );
939
+ }
940
+ /**
941
+ * Create error for encoding failure
942
+ */
943
+ static encodingFailed(source, error) {
944
+ return new _SchemaValidationError(
945
+ `Failed to encode schema: ${error.message}`,
946
+ void 0,
947
+ [],
948
+ source,
949
+ error
950
+ );
951
+ }
952
+ /**
953
+ * Create error for registry operation failure
954
+ */
955
+ static registryError(operation, details) {
956
+ return new _SchemaValidationError(`Schema registry ${operation} failed: ${details}`);
957
+ }
958
+ /**
959
+ * Format error for display
960
+ */
961
+ format() {
962
+ let output = this.message;
963
+ if (this.schemaId) {
964
+ output += `
965
+ Schema ID: ${this.schemaId}`;
966
+ }
967
+ if (this.diagnostics.length > 0) {
968
+ output += "\n\nValidation Issues:";
969
+ this.diagnostics.forEach((diag, index) => {
970
+ output += `
971
+ ${index + 1}. [${diag.severity}] ${diag.message}`;
972
+ if (diag.pointer) {
973
+ output += ` at ${diag.pointer}`;
974
+ }
975
+ if (diag.keyword) {
976
+ output += ` (keyword: ${diag.keyword})`;
977
+ }
978
+ if (diag.source) {
979
+ output += ` [${diag.source}]`;
980
+ }
981
+ });
982
+ }
983
+ if (this.source) {
984
+ output += `
985
+
986
+ Source: ${this.source.type}`;
987
+ if (this.source.id) {
988
+ output += ` (${this.source.id})`;
989
+ }
990
+ }
991
+ return output;
992
+ }
993
+ /**
994
+ * Convert to JSON representation
995
+ */
996
+ toJSON() {
997
+ return {
998
+ name: this.name,
999
+ message: this.message,
1000
+ schemaId: this.schemaId,
1001
+ diagnostics: this.diagnostics,
1002
+ source: this.source,
1003
+ cause: this.cause?.message
1004
+ };
1005
+ }
1006
+ };
1007
+ }
1008
+ });
1009
+
1010
+ // src/schema/utils.ts
1011
+ function createDiagnostic(pointer, message, keyword, severity = "ERROR", source = "ajv", data) {
1012
+ return {
1013
+ pointer,
1014
+ message,
1015
+ keyword,
1016
+ severity,
1017
+ source,
1018
+ data
1019
+ };
1020
+ }
1021
+ var init_utils = __esm({
1022
+ "src/schema/utils.ts"() {
1023
+ init_errors();
1024
+ }
1025
+ });
1026
+ var init_goneat_bridge = __esm({
1027
+ "src/schema/goneat-bridge.ts"() {
1028
+ init_utils();
1029
+ }
1030
+ });
1031
+ var init_normalizer = __esm({
1032
+ "src/schema/normalizer.ts"() {
1033
+ init_errors();
1034
+ }
1035
+ });
1036
+ function optionsChanged(newOptions) {
1037
+ if (!newOptions && !globalRegistryOptions) return false;
1038
+ if (!newOptions || !globalRegistryOptions) return true;
1039
+ return newOptions.baseDir !== globalRegistryOptions.baseDir || JSON.stringify(newOptions.patterns) !== JSON.stringify(globalRegistryOptions.patterns) || newOptions.followSymlinks !== globalRegistryOptions.followSymlinks || newOptions.maxDepth !== globalRegistryOptions.maxDepth;
1040
+ }
1041
+ function getSchemaRegistry(options) {
1042
+ if (!globalRegistry || optionsChanged(options)) {
1043
+ globalRegistry = new SchemaRegistry(options);
1044
+ globalRegistryOptions = options;
1045
+ }
1046
+ return globalRegistry;
1047
+ }
1048
+ var DEFAULT_PATTERNS, SchemaRegistry, globalRegistry, globalRegistryOptions;
1049
+ var init_registry2 = __esm({
1050
+ "src/schema/registry.ts"() {
1051
+ init_errors();
1052
+ DEFAULT_PATTERNS = ["**/*.schema.json", "**/*.schema.yaml", "**/*.schema.yml"];
1053
+ SchemaRegistry = class {
1054
+ schemas = /* @__PURE__ */ new Map();
1055
+ options;
1056
+ constructor(options = {}) {
1057
+ this.options = {
1058
+ baseDir: options.baseDir || this.getDefaultSchemaDir(),
1059
+ patterns: options.patterns || DEFAULT_PATTERNS,
1060
+ followSymlinks: options.followSymlinks ?? false,
1061
+ maxDepth: options.maxDepth ?? 10
1062
+ };
1063
+ }
1064
+ /**
1065
+ * Get default schema directory using import.meta.url
1066
+ */
1067
+ getDefaultSchemaDir() {
1068
+ const __filename2 = fileURLToPath(import.meta.url);
1069
+ const __dirname3 = dirname(__filename2);
1070
+ return join(__dirname3, "..", "..", "schemas", "crucible-ts");
1071
+ }
1072
+ /**
1073
+ * Build logical schema ID from file path
1074
+ */
1075
+ buildSchemaId(filePath, baseDir) {
1076
+ const relativePath = relative(baseDir, filePath);
1077
+ const withoutExt = relativePath.replace(/\.(schema\.(json|yaml|yml))$/, "");
1078
+ return withoutExt.replace(/\\/g, "/");
1079
+ }
1080
+ /**
1081
+ * Extract schema format from file extension
1082
+ */
1083
+ getSchemaFormat(filePath) {
1084
+ const ext = extname(filePath).toLowerCase();
1085
+ switch (ext) {
1086
+ case ".json":
1087
+ return "json";
1088
+ case ".yaml":
1089
+ case ".yml":
1090
+ return "yaml";
1091
+ default:
1092
+ return "json";
1093
+ }
1094
+ }
1095
+ /**
1096
+ * Extract metadata from schema file
1097
+ */
1098
+ async extractMetadata(filePath) {
1099
+ try {
1100
+ const content = await readFile(filePath, "utf-8");
1101
+ const format = this.getSchemaFormat(filePath);
1102
+ let parsed;
1103
+ if (format === "yaml") {
1104
+ parsed = parse(content);
1105
+ } else {
1106
+ parsed = JSON.parse(content);
1107
+ }
1108
+ const baseDir = this.options.baseDir ?? "";
1109
+ const relativePath = relative(baseDir, filePath);
1110
+ return {
1111
+ id: this.buildSchemaId(filePath, baseDir),
1112
+ path: filePath,
1113
+ relativePath,
1114
+ format,
1115
+ version: parsed.$schema || parsed.version,
1116
+ description: parsed.title || parsed.description,
1117
+ schemaDraft: parsed.$schema
1118
+ };
1119
+ } catch (error) {
1120
+ throw SchemaValidationError.registryError(
1121
+ "metadata extraction",
1122
+ `Failed to process ${filePath}: ${error.message}`
1123
+ );
1124
+ }
1125
+ }
1126
+ /**
1127
+ * Discover and index all available schemas
1128
+ */
1129
+ async discoverSchemas() {
1130
+ try {
1131
+ const baseDir = this.options.baseDir ?? "";
1132
+ const patterns = this.options.patterns ?? [];
1133
+ if (patterns.length === 0) {
1134
+ this.schemas.clear();
1135
+ return;
1136
+ }
1137
+ const pattern = patterns.map((p) => join(baseDir, p));
1138
+ try {
1139
+ await access(baseDir);
1140
+ } catch {
1141
+ this.schemas.clear();
1142
+ return;
1143
+ }
1144
+ const files = await glob(pattern, {
1145
+ absolute: true,
1146
+ followSymbolicLinks: this.options.followSymlinks,
1147
+ deep: this.options.maxDepth,
1148
+ onlyFiles: true,
1149
+ suppressErrors: true
1150
+ // Don't throw on permission errors
1151
+ });
1152
+ this.schemas.clear();
1153
+ for (const filePath of files) {
1154
+ try {
1155
+ const metadata = await this.extractMetadata(filePath);
1156
+ this.schemas.set(metadata.id, metadata);
1157
+ } catch (error) {
1158
+ console.warn(`Warning: Failed to process schema ${filePath}:`, error);
1159
+ }
1160
+ }
1161
+ } catch (error) {
1162
+ throw SchemaValidationError.registryError("discovery", error.message);
1163
+ }
1164
+ }
1165
+ /**
1166
+ * List available schemas with optional prefix filtering
1167
+ */
1168
+ async listSchemas(prefix) {
1169
+ if (this.schemas.size === 0) {
1170
+ await this.discoverSchemas();
1171
+ }
1172
+ const schemas = Array.from(this.schemas.values());
1173
+ if (prefix) {
1174
+ return schemas.filter((schema) => schema.id.startsWith(prefix));
1175
+ }
1176
+ return schemas;
1177
+ }
1178
+ /**
1179
+ * Get schema by logical ID
1180
+ */
1181
+ async getSchema(id) {
1182
+ if (this.schemas.size === 0) {
1183
+ await this.discoverSchemas();
1184
+ }
1185
+ const schema = this.schemas.get(id);
1186
+ if (!schema) {
1187
+ throw SchemaValidationError.schemaNotFound(id);
1188
+ }
1189
+ return schema;
1190
+ }
1191
+ /**
1192
+ * Get schema by file path
1193
+ */
1194
+ async getSchemaByPath(filePath) {
1195
+ if (this.schemas.size === 0) {
1196
+ await this.discoverSchemas();
1197
+ }
1198
+ const absolutePath = filePath.startsWith("/") ? filePath : join(process.cwd(), filePath);
1199
+ for (const schema of this.schemas.values()) {
1200
+ if (schema.path === absolutePath) {
1201
+ return schema;
1202
+ }
1203
+ }
1204
+ throw SchemaValidationError.schemaNotFound(filePath);
1205
+ }
1206
+ /**
1207
+ * Check if schema exists
1208
+ */
1209
+ async hasSchema(id) {
1210
+ if (this.schemas.size === 0) {
1211
+ await this.discoverSchemas();
1212
+ }
1213
+ return this.schemas.has(id);
1214
+ }
1215
+ /**
1216
+ * Get registry size
1217
+ */
1218
+ get size() {
1219
+ return this.schemas.size;
1220
+ }
1221
+ /**
1222
+ * Clear registry cache
1223
+ */
1224
+ clear() {
1225
+ this.schemas.clear();
1226
+ }
1227
+ };
1228
+ }
1229
+ });
1230
+ var init_cli = __esm({
1231
+ "src/schema/cli.ts"() {
1232
+ init_goneat_bridge();
1233
+ init_normalizer();
1234
+ init_registry2();
1235
+ init_utils();
1236
+ init_validator();
1237
+ }
1238
+ });
1239
+ var init_export = __esm({
1240
+ "src/schema/export.ts"() {
1241
+ init_errors();
1242
+ init_registry2();
1243
+ init_validator();
1244
+ }
1245
+ });
1246
+
1247
+ // src/schema/index.ts
1248
+ var init_schema = __esm({
1249
+ "src/schema/index.ts"() {
1250
+ init_ajv_formats();
1251
+ init_cli();
1252
+ init_errors();
1253
+ init_export();
1254
+ init_goneat_bridge();
1255
+ init_normalizer();
1256
+ init_registry2();
1257
+ init_utils();
1258
+ init_validator();
1259
+ }
1260
+ });
1261
+
1262
+ // src/telemetry/validators.ts
1263
+ var init_validators = __esm({
1264
+ "src/telemetry/validators.ts"() {
1265
+ init_schema();
1266
+ }
1267
+ });
1268
+
1269
+ // src/telemetry/index.ts
1270
+ var metrics;
1271
+ var init_telemetry = __esm({
1272
+ "src/telemetry/index.ts"() {
1273
+ init_registry();
1274
+ init_registry();
1275
+ init_counter();
1276
+ init_gauge();
1277
+ init_histogram();
1278
+ init_taxonomy();
1279
+ init_types();
1280
+ init_validators();
1281
+ metrics = new MetricsRegistry();
1282
+ }
1283
+ });
1284
+
1285
+ // src/schema/validator.ts
1286
+ var validator_exports = {};
1287
+ __export(validator_exports, {
1288
+ clearCache: () => clearCache,
1289
+ compileSchema: () => compileSchema,
1290
+ compileSchemaById: () => compileSchemaById,
1291
+ getCacheSize: () => getCacheSize,
1292
+ validateData: () => validateData,
1293
+ validateDataBySchemaId: () => validateDataBySchemaId,
1294
+ validateFile: () => validateFile,
1295
+ validateFileBySchemaId: () => validateFileBySchemaId,
1296
+ validateSchema: () => validateSchema
1297
+ });
1298
+ async function loadMetaSchema(draft) {
1299
+ const __filename2 = fileURLToPath(import.meta.url);
1300
+ const __dirname3 = dirname(__filename2);
1301
+ const metaSchemaPath = join(
1302
+ __dirname3,
1303
+ "..",
1304
+ "..",
1305
+ "schemas",
1306
+ "crucible-ts",
1307
+ "meta",
1308
+ draft,
1309
+ "schema.json"
1310
+ );
1311
+ const content = await readFile(metaSchemaPath, "utf-8");
1312
+ return JSON.parse(content);
1313
+ }
1314
+ async function loadVocabularySchemas(draft) {
1315
+ if (draft !== "draft-2019-09" && draft !== "draft-2020-12") {
1316
+ return [];
1317
+ }
1318
+ const __filename2 = fileURLToPath(import.meta.url);
1319
+ const __dirname3 = dirname(__filename2);
1320
+ const vocabDir = join(__dirname3, "..", "..", "schemas", "crucible-ts", "meta", draft, "meta");
1321
+ const vocabFiles = draft === "draft-2020-12" ? [
1322
+ "core.json",
1323
+ "applicator.json",
1324
+ "unevaluated.json",
1325
+ "validation.json",
1326
+ "meta-data.json",
1327
+ "format-annotation.json",
1328
+ "content.json"
1329
+ ] : [
1330
+ "core.json",
1331
+ "applicator.json",
1332
+ "validation.json",
1333
+ "meta-data.json",
1334
+ "format.json",
1335
+ "content.json"
1336
+ ];
1337
+ const schemas = [];
1338
+ for (const file of vocabFiles) {
1339
+ try {
1340
+ const content = await readFile(join(vocabDir, file), "utf-8");
1341
+ schemas.push(JSON.parse(content));
1342
+ } catch {
1343
+ }
1344
+ }
1345
+ return schemas;
1346
+ }
1347
+ async function loadReferencedSchema(uri) {
1348
+ const __filename2 = fileURLToPath(import.meta.url);
1349
+ const __dirname3 = dirname(__filename2);
1350
+ const repoRoot = join(__dirname3, "..", "..");
1351
+ let resolvedPath;
1352
+ if (uri.startsWith("https://schemas.fulmenhq.dev/")) {
1353
+ let relativePath = uri.replace("https://schemas.fulmenhq.dev/", "");
1354
+ if (relativePath.startsWith("crucible/")) {
1355
+ relativePath = relativePath.slice("crucible/".length);
1356
+ }
1357
+ if (relativePath.startsWith("config/taxonomy/")) {
1358
+ resolvedPath = join(
1359
+ repoRoot,
1360
+ "config",
1361
+ "crucible-ts",
1362
+ "taxonomy",
1363
+ relativePath.split("/").pop() || ""
1364
+ );
1365
+ } else {
1366
+ resolvedPath = join(repoRoot, "schemas", "crucible-ts", relativePath);
1367
+ }
1368
+ } else if (uri.startsWith("../../") || uri.startsWith("../")) {
1369
+ const schemaBase = join(
1370
+ repoRoot,
1371
+ "schemas",
1372
+ "crucible-ts",
1373
+ "observability",
1374
+ "metrics",
1375
+ "v1.0.0"
1376
+ );
1377
+ resolvedPath = join(schemaBase, uri);
1378
+ } else if (uri.startsWith("file://")) {
1379
+ resolvedPath = fileURLToPath(uri);
1380
+ } else {
1381
+ throw new Error(`Cannot load remote schema: ${uri}`);
1382
+ }
1383
+ const content = await readFile(resolvedPath, "utf-8");
1384
+ const ext = resolvedPath.split(".").pop()?.toLowerCase();
1385
+ if (ext === "yaml" || ext === "yml") {
1386
+ return parse(content);
1387
+ }
1388
+ return JSON.parse(content);
1389
+ }
1390
+ function detectDialect(schema) {
1391
+ if (schema && typeof schema === "object" && !Array.isArray(schema)) {
1392
+ const maybeSchema = schema;
1393
+ const declared = maybeSchema.$schema;
1394
+ if (typeof declared === "string") {
1395
+ if (declared.includes("draft-04")) return "draft-04";
1396
+ if (declared.includes("draft-06")) return "draft-06";
1397
+ if (declared.includes("draft-07")) return "draft-07";
1398
+ if (declared.includes("draft/2019-09")) return "draft-2019-09";
1399
+ if (declared.includes("draft/2020-12")) return "draft-2020-12";
1400
+ }
1401
+ }
1402
+ return "draft-2020-12";
1403
+ }
1404
+ function createAjv(dialect) {
1405
+ const AjvCtor = dialect === "draft-2020-12" ? Ajv2020 : dialect === "draft-2019-09" ? Ajv2019 : dialect === "draft-04" ? AjvDraft04 : Ajv;
1406
+ const ajv = new AjvCtor({
1407
+ strict: false,
1408
+ allErrors: true,
1409
+ verbose: true,
1410
+ // Allow schemas with $id to be added without replacing existing ones
1411
+ addUsedSchema: false,
1412
+ // draft-04 uses "id"; later drafts use "$id"
1413
+ schemaId: dialect === "draft-04" ? "id" : "$id",
1414
+ // Enable async schema loading for YAML references
1415
+ loadSchema: loadReferencedSchema
1416
+ });
1417
+ applyFulmenAjvFormats(ajv);
1418
+ return ajv;
1419
+ }
1420
+ async function getAjv(dialect) {
1421
+ const existing = ajvInstances.get(dialect);
1422
+ if (existing) {
1423
+ const ready = metaschemaReady.get(dialect);
1424
+ if (ready) await ready;
1425
+ return existing;
1426
+ }
1427
+ const ajv = createAjv(dialect);
1428
+ ajvInstances.set(dialect, ajv);
1429
+ const readyPromise = Promise.all([loadVocabularySchemas(dialect), loadMetaSchema(dialect)]).then(([vocabSchemas, metaSchema]) => {
1430
+ for (const vocabSchema of vocabSchemas) {
1431
+ try {
1432
+ ajv.addMetaSchema(vocabSchema);
1433
+ } catch {
1434
+ }
1435
+ }
1436
+ try {
1437
+ ajv.addMetaSchema(metaSchema);
1438
+ } catch {
1439
+ }
1440
+ }).catch((error) => {
1441
+ throw new Error(`Failed to load metaschemas (${dialect}): ${error}`);
1442
+ });
1443
+ metaschemaReady.set(dialect, readyPromise);
1444
+ await readyPromise;
1445
+ return ajv;
1446
+ }
1447
+ async function compileSchema(schema, options = {}) {
1448
+ const baseKey = typeof schema === "string" ? schema : JSON.stringify(schema);
1449
+ let parsedSchema;
1450
+ if (typeof schema === "string") {
1451
+ try {
1452
+ parsedSchema = JSON.parse(schema);
1453
+ } catch {
1454
+ parsedSchema = parse(schema);
1455
+ }
1456
+ } else if (Buffer.isBuffer(schema)) {
1457
+ const content = schema.toString("utf-8");
1458
+ try {
1459
+ parsedSchema = JSON.parse(content);
1460
+ } catch {
1461
+ parsedSchema = parse(content);
1462
+ }
1463
+ } else {
1464
+ parsedSchema = schema;
1465
+ }
1466
+ const dialect = detectDialect(parsedSchema);
1467
+ const ajv = await getAjv(dialect);
1468
+ const cacheKey = `${dialect}:${baseKey}`;
1469
+ const cached = schemaCache.get(cacheKey);
1470
+ if (cached !== void 0) {
1471
+ return cached;
1472
+ }
1473
+ try {
1474
+ if (options.aliases && options.aliases.length > 0) {
1475
+ for (const alias of options.aliases) {
1476
+ if (alias && ajv.getSchema(alias) === void 0) {
1477
+ try {
1478
+ if (typeof parsedSchema === "object" && parsedSchema !== null) {
1479
+ ajv.addSchema(parsedSchema, alias);
1480
+ }
1481
+ } catch {
1482
+ }
1483
+ }
1484
+ }
1485
+ }
1486
+ const validator = typeof parsedSchema === "boolean" ? ajv.compile(parsedSchema) : await ajv.compileAsync(parsedSchema);
1487
+ schemaCache.set(cacheKey, validator);
1488
+ return validator;
1489
+ } catch (error) {
1490
+ throw SchemaValidationError.parseFailed(
1491
+ {
1492
+ type: "string",
1493
+ content: typeof schema === "string" ? schema : JSON.stringify(schema)
1494
+ },
1495
+ error
1496
+ );
1497
+ }
1498
+ }
1499
+ function validateData(data, validator) {
1500
+ const valid = validator(data);
1501
+ const result = {
1502
+ valid,
1503
+ diagnostics: [],
1504
+ source: "ajv"
1505
+ };
1506
+ if (!valid && validator.errors) {
1507
+ const errors = validator.errors;
1508
+ if (Array.isArray(errors)) {
1509
+ result.diagnostics = errors.map(
1510
+ (error) => createDiagnostic(
1511
+ error.instancePath || "",
1512
+ error.message || "Validation failed",
1513
+ error.keyword || "unknown",
1514
+ "ERROR",
1515
+ "ajv"
1516
+ )
1517
+ );
1518
+ }
1519
+ metrics.counter("schema_validation_errors").inc();
1520
+ } else {
1521
+ metrics.counter("schema_validations").inc();
1522
+ }
1523
+ return result;
1524
+ }
1525
+ async function validateFile(filePath, validator) {
1526
+ try {
1527
+ const content = await readFile(filePath, "utf-8");
1528
+ let data;
1529
+ try {
1530
+ data = JSON.parse(content);
1531
+ } catch {
1532
+ data = parse(content);
1533
+ }
1534
+ return validateData(data, validator);
1535
+ } catch (error) {
1536
+ if (error instanceof SchemaValidationError) {
1537
+ throw error;
1538
+ }
1539
+ throw SchemaValidationError.validationFailed(
1540
+ filePath,
1541
+ [
1542
+ createDiagnostic(
1543
+ "",
1544
+ `Failed to read or parse file: ${error.message}`,
1545
+ "file-read",
1546
+ "ERROR",
1547
+ "ajv"
1548
+ )
1549
+ ],
1550
+ { type: "file", id: filePath }
1551
+ );
1552
+ }
1553
+ }
1554
+ async function validateSchema(schema) {
1555
+ try {
1556
+ let parsedSchema;
1557
+ if (typeof schema === "string") {
1558
+ try {
1559
+ parsedSchema = JSON.parse(schema);
1560
+ } catch {
1561
+ parsedSchema = parse(schema);
1562
+ }
1563
+ } else if (Buffer.isBuffer(schema)) {
1564
+ const content = schema.toString("utf-8");
1565
+ try {
1566
+ parsedSchema = JSON.parse(content);
1567
+ } catch {
1568
+ parsedSchema = parse(content);
1569
+ }
1570
+ } else {
1571
+ parsedSchema = schema;
1572
+ }
1573
+ const dialect = detectDialect(parsedSchema);
1574
+ const ajv = await getAjv(dialect);
1575
+ const metaValid = ajv.validateSchema(parsedSchema);
1576
+ if (!metaValid && ajv.errors) {
1577
+ const diagnostics = ajv.errors.map(
1578
+ (error) => createDiagnostic(
1579
+ error.instancePath || "",
1580
+ error.message || "Schema meta-validation failed",
1581
+ error.keyword || "unknown",
1582
+ "ERROR",
1583
+ "ajv"
1584
+ )
1585
+ );
1586
+ return { valid: false, diagnostics, source: "ajv" };
1587
+ }
1588
+ await compileSchema(parsedSchema);
1589
+ return {
1590
+ valid: true,
1591
+ diagnostics: [],
1592
+ source: "ajv"
1593
+ };
1594
+ } catch (error) {
1595
+ if (error instanceof SchemaValidationError) {
1596
+ return {
1597
+ valid: false,
1598
+ diagnostics: error.diagnostics,
1599
+ source: "ajv"
1600
+ };
1601
+ }
1602
+ return {
1603
+ valid: false,
1604
+ diagnostics: [
1605
+ createDiagnostic(
1606
+ "",
1607
+ `Schema validation failed: ${error.message}`,
1608
+ "schema-validation",
1609
+ "ERROR",
1610
+ "ajv"
1611
+ )
1612
+ ],
1613
+ source: "ajv"
1614
+ };
1615
+ }
1616
+ }
1617
+ function clearCache() {
1618
+ schemaCache.clear();
1619
+ }
1620
+ function getCacheSize() {
1621
+ return schemaCache.size;
1622
+ }
1623
+ async function compileSchemaById(schemaId, registryOptions) {
1624
+ try {
1625
+ const registry = getSchemaRegistry(registryOptions);
1626
+ const metadata = await registry.getSchema(schemaId);
1627
+ const content = await readFile(metadata.path, "utf-8");
1628
+ const aliases = [];
1629
+ const normalizedRelativePath = metadata.relativePath.replace(/\\/g, "/");
1630
+ if (normalizedRelativePath) {
1631
+ aliases.push(
1632
+ new URL(`crucible/${normalizedRelativePath}`, "https://schemas.fulmenhq.dev/").toString()
1633
+ );
1634
+ }
1635
+ return compileSchema(content, { aliases });
1636
+ } catch (error) {
1637
+ metrics.counter("schema_validation_errors").inc();
1638
+ throw error;
1639
+ }
1640
+ }
1641
+ async function validateDataBySchemaId(data, schemaId, registryOptions) {
1642
+ try {
1643
+ const validator = await compileSchemaById(schemaId, registryOptions);
1644
+ return validateData(data, validator);
1645
+ } catch (error) {
1646
+ metrics.counter("schema_validation_errors").inc();
1647
+ throw error;
1648
+ }
1649
+ }
1650
+ async function validateFileBySchemaId(filePath, schemaId, registryOptions) {
1651
+ try {
1652
+ const validator = await compileSchemaById(schemaId, registryOptions);
1653
+ return validateFile(filePath, validator);
1654
+ } catch (error) {
1655
+ metrics.counter("schema_validation_errors").inc();
1656
+ throw error;
1657
+ }
1658
+ }
1659
+ var ajvInstances, metaschemaReady, schemaCache;
1660
+ var init_validator = __esm({
1661
+ "src/schema/validator.ts"() {
1662
+ init_telemetry();
1663
+ init_ajv_formats();
1664
+ init_errors();
1665
+ init_registry2();
1666
+ init_utils();
1667
+ ajvInstances = /* @__PURE__ */ new Map();
1668
+ metaschemaReady = /* @__PURE__ */ new Map();
1669
+ schemaCache = /* @__PURE__ */ new Map();
1670
+ }
1671
+ });
1672
+
1673
+ // src/foundry/errors.ts
1674
+ var FoundryCatalogError;
1675
+ var init_errors2 = __esm({
1676
+ "src/foundry/errors.ts"() {
1677
+ FoundryCatalogError = class _FoundryCatalogError extends Error {
1678
+ constructor(message, catalog, cause) {
1679
+ super(message);
1680
+ this.catalog = catalog;
1681
+ this.cause = cause;
1682
+ this.name = "FoundryCatalogError";
1683
+ if (Error.captureStackTrace) {
1684
+ Error.captureStackTrace(this, _FoundryCatalogError);
1685
+ }
1686
+ }
1687
+ static invalidSchema(catalog, details, cause) {
1688
+ return new _FoundryCatalogError(
1689
+ `Invalid schema in ${catalog} catalog: ${details}`,
1690
+ catalog,
1691
+ cause
1692
+ );
1693
+ }
1694
+ static missingCatalog(catalog) {
1695
+ return new _FoundryCatalogError(`Catalog ${catalog} not found or could not be loaded`, catalog);
1696
+ }
1697
+ static invalidPattern(patternId, details) {
1698
+ return new _FoundryCatalogError(`Invalid pattern ${patternId}: ${details}`, "patterns");
1699
+ }
1700
+ static compilationError(patternId, details, cause) {
1701
+ return new _FoundryCatalogError(
1702
+ `Failed to compile pattern ${patternId}: ${details}`,
1703
+ "patterns",
1704
+ cause
1705
+ );
1706
+ }
1707
+ };
1708
+ }
1709
+ });
1710
+ function getConfigPath() {
1711
+ if (__dirname2.includes("/dist/")) {
1712
+ return join(__dirname2, "../../config/crucible-ts/library/foundry/signals.yaml");
1713
+ }
1714
+ return join(__dirname2, "../../../config/crucible-ts/library/foundry/signals.yaml");
1715
+ }
1716
+ async function loadCatalog() {
1717
+ const filePath = SSOT_PATHS.signals;
1718
+ const catalogName = "signals";
1719
+ try {
1720
+ let content;
1721
+ if (typeof Bun !== "undefined") {
1722
+ try {
1723
+ const file = Bun.file(filePath);
1724
+ if (!await file.exists()) {
1725
+ throw FoundryCatalogError.missingCatalog(catalogName);
1726
+ }
1727
+ content = await file.text();
1728
+ } catch (error) {
1729
+ if (error instanceof Error && error.message.includes("No such file")) {
1730
+ throw FoundryCatalogError.missingCatalog(catalogName);
1731
+ }
1732
+ throw error;
1733
+ }
1734
+ } else {
1735
+ try {
1736
+ content = await readFile(filePath, "utf-8");
1737
+ } catch (error) {
1738
+ if (error.code === "ENOENT") {
1739
+ throw FoundryCatalogError.missingCatalog(catalogName);
1740
+ }
1741
+ throw error;
1742
+ }
1743
+ }
1744
+ const data = parse(content);
1745
+ const result = await validateDataBySchemaId(data, SCHEMA_ID);
1746
+ if (!result.valid) {
1747
+ const errorMessages = result.diagnostics.map((d) => `${d.pointer}: ${d.message}`).join("; ");
1748
+ throw FoundryCatalogError.invalidSchema(
1749
+ catalogName,
1750
+ `Schema validation failed: ${errorMessages}`
1751
+ );
1752
+ }
1753
+ return data;
1754
+ } catch (error) {
1755
+ if (error instanceof FoundryCatalogError) {
1756
+ throw error;
1757
+ }
1758
+ const err = error;
1759
+ if (err.code === "ENOENT") {
1760
+ throw FoundryCatalogError.missingCatalog(catalogName);
1761
+ } else if (err.code === "EACCES") {
1762
+ throw FoundryCatalogError.invalidSchema(
1763
+ catalogName,
1764
+ "Permission denied accessing catalog file",
1765
+ err
1766
+ );
1767
+ } else if (err.code === "EISDIR") {
1768
+ throw FoundryCatalogError.invalidSchema(
1769
+ catalogName,
1770
+ "Expected file but found directory",
1771
+ err
1772
+ );
1773
+ } else if (err.code === "EMFILE" || err.code === "ENFILE") {
1774
+ throw FoundryCatalogError.invalidSchema(catalogName, "Too many open files", err);
1775
+ }
1776
+ throw FoundryCatalogError.invalidSchema(catalogName, "Failed to load catalog", err);
1777
+ }
1778
+ }
1779
+ async function getCatalog() {
1780
+ if (!cachedCatalog) {
1781
+ cachedCatalog = await loadCatalog();
1782
+ }
1783
+ return cachedCatalog;
1784
+ }
1785
+ async function getSignalsVersion() {
1786
+ const catalog = await getCatalog();
1787
+ return catalog.version;
1788
+ }
1789
+ async function listSignals() {
1790
+ const catalog = await getCatalog();
1791
+ return catalog.signals.map((signal) => ({ ...signal }));
1792
+ }
1793
+ async function getSignal(identifier) {
1794
+ const catalog = await getCatalog();
1795
+ const signal = catalog.signals.find((s) => s.id === identifier || s.name === identifier);
1796
+ return signal ? { ...signal } : null;
1797
+ }
1798
+ async function listBehaviors() {
1799
+ const catalog = await getCatalog();
1800
+ return catalog.behaviors.map((behavior) => ({ ...behavior }));
1801
+ }
1802
+ async function getBehavior(id) {
1803
+ const catalog = await getCatalog();
1804
+ const behavior = catalog.behaviors.find((b) => b.id === id);
1805
+ return behavior ? { ...behavior } : null;
1806
+ }
1807
+ async function getSignalCatalog() {
1808
+ return await getCatalog();
1809
+ }
1810
+ var __filename, __dirname2, SSOT_PATHS, SCHEMA_ID, cachedCatalog;
1811
+ var init_catalog = __esm({
1812
+ "src/foundry/signals/catalog.ts"() {
1813
+ init_validator();
1814
+ init_errors2();
1815
+ __filename = fileURLToPath(import.meta.url);
1816
+ __dirname2 = dirname(__filename);
1817
+ SSOT_PATHS = {
1818
+ signals: getConfigPath()
1819
+ };
1820
+ SCHEMA_ID = "library/foundry/v1.0.0/signals";
1821
+ cachedCatalog = null;
1822
+ }
1823
+ });
1824
+
1825
+ // src/foundry/signals/capabilities.ts
1826
+ function getPlatform2() {
1827
+ const platform = process.platform;
1828
+ switch (platform) {
1829
+ case "linux":
1830
+ return "linux";
1831
+ case "darwin":
1832
+ return "darwin";
1833
+ case "win32":
1834
+ return "win32";
1835
+ case "freebsd":
1836
+ return "freebsd";
1837
+ default:
1838
+ return "unknown";
1839
+ }
1840
+ }
1841
+ function isPOSIX2() {
1842
+ const platform = getPlatform2();
1843
+ return platform === "linux" || platform === "darwin" || platform === "freebsd";
1844
+ }
1845
+ function isWindows2() {
1846
+ return getPlatform2() === "win32";
1847
+ }
1848
+ async function supportsSignal(signalName) {
1849
+ const signal = await getSignal(signalName);
1850
+ if (!signal) {
1851
+ return false;
1852
+ }
1853
+ if (isPOSIX2()) {
1854
+ return true;
1855
+ }
1856
+ if (isWindows2()) {
1857
+ return signal.windows_event !== null;
1858
+ }
1859
+ return false;
1860
+ }
1861
+ function supportsSignalExitCodes2() {
1862
+ return isPOSIX2();
1863
+ }
1864
+ async function getPlatformCapabilities2() {
1865
+ const platform = getPlatform2();
1866
+ const isPosix = isPOSIX2();
1867
+ const isWin = isWindows2();
1868
+ const catalog = await getSignalCatalog();
1869
+ const supported = [];
1870
+ const unsupported = [];
1871
+ const mapped = [];
1872
+ for (const signal of catalog.signals) {
1873
+ if (isPosix) {
1874
+ supported.push(signal.name);
1875
+ } else if (isWin) {
1876
+ if (signal.windows_event !== null) {
1877
+ supported.push(signal.name);
1878
+ mapped.push(signal.name);
1879
+ } else {
1880
+ unsupported.push(signal.name);
1881
+ }
1882
+ } else {
1883
+ unsupported.push(signal.name);
1884
+ }
1885
+ }
1886
+ return {
1887
+ platform,
1888
+ isPOSIX: isPosix,
1889
+ isWindows: isWin,
1890
+ supportsNativeSignals: isPosix,
1891
+ supportsSignalExitCodes: supportsSignalExitCodes2(),
1892
+ supportedSignals: supported,
1893
+ unsupportedSignals: unsupported,
1894
+ mappedSignals: mapped
1895
+ };
1896
+ }
1897
+ var init_capabilities2 = __esm({
1898
+ "src/foundry/signals/capabilities.ts"() {
1899
+ init_catalog();
1900
+ }
1901
+ });
1902
+
1903
+ // src/foundry/signals/cli.ts
1904
+ init_exit_codes();
1905
+ init_capabilities2();
1906
+ init_catalog();
1907
+ function createSignalsCLI() {
1908
+ const program = new Command();
1909
+ program.name("tsfulmen-signals").description("Signal handling CLI for Fulmen (developer tool)").version("0.1.0");
1910
+ program.command("show").description("Show signal catalog information").argument("[signal]", "Signal name to show (e.g., SIGTERM, TERM, HUP)").option("--json", "Output as JSON").option("--behaviors", "Show behaviors instead of signals").action(async (signal, cmdOptions) => {
1911
+ try {
1912
+ if (cmdOptions?.behaviors) {
1913
+ if (signal) {
1914
+ const behavior = await getBehavior(signal);
1915
+ if (!behavior) {
1916
+ console.error(`Behavior '${signal}' not found`);
1917
+ process.exit(exitCodes.EXIT_INVALID_ARGUMENT);
1918
+ }
1919
+ if (cmdOptions.json) {
1920
+ console.log(JSON.stringify(behavior, null, 2));
1921
+ } else {
1922
+ console.log(`Behavior: ${behavior.id}
1923
+ `);
1924
+ console.log(` Name: ${behavior.name}`);
1925
+ console.log(` Description: ${behavior.description}`);
1926
+ console.log(` Phases: ${behavior.phases.join(", ")}`);
1927
+ }
1928
+ } else {
1929
+ const behaviors = await listBehaviors();
1930
+ if (cmdOptions.json) {
1931
+ console.log(JSON.stringify(behaviors, null, 2));
1932
+ } else {
1933
+ console.log(`Found ${behaviors.length} behavior(s):
1934
+ `);
1935
+ for (const behavior of behaviors) {
1936
+ console.log(` ${behavior.id}: ${behavior.description}`);
1937
+ }
1938
+ }
1939
+ }
1940
+ return;
1941
+ }
1942
+ if (signal) {
1943
+ const normalizedSignal = signal.toUpperCase().startsWith("SIG") ? signal.toUpperCase() : `SIG${signal.toUpperCase()}`;
1944
+ const signalInfo = await getSignal(normalizedSignal);
1945
+ if (!signalInfo) {
1946
+ console.error(`Signal '${signal}' not found`);
1947
+ process.exit(exitCodes.EXIT_INVALID_ARGUMENT);
1948
+ }
1949
+ const supported = await supportsSignal(normalizedSignal);
1950
+ const caps = await getPlatformCapabilities2();
1951
+ if (cmdOptions?.json) {
1952
+ console.log(
1953
+ JSON.stringify(
1954
+ {
1955
+ ...signalInfo,
1956
+ platform_supported: supported,
1957
+ platform_capabilities: caps
1958
+ },
1959
+ null,
1960
+ 2
1961
+ )
1962
+ );
1963
+ } else {
1964
+ console.log(`Signal: ${signalInfo.name}
1965
+ `);
1966
+ console.log(` Description: ${signalInfo.description}`);
1967
+ console.log(` Number (POSIX): ${signalInfo.unix_number}`);
1968
+ console.log(` Default Behavior: ${signalInfo.default_behavior}`);
1969
+ console.log(` Exit Code: ${signalInfo.exit_code}`);
1970
+ console.log(` Platform Supported: ${supported ? "Yes" : "No (use HTTP fallback)"}`);
1971
+ if (signalInfo.platform_overrides) {
1972
+ console.log(`
1973
+ Platform Overrides:`);
1974
+ if (signalInfo.platform_overrides.darwin)
1975
+ console.log(` macOS: ${JSON.stringify(signalInfo.platform_overrides.darwin)}`);
1976
+ if (signalInfo.platform_overrides.freebsd)
1977
+ console.log(
1978
+ ` FreeBSD: ${JSON.stringify(signalInfo.platform_overrides.freebsd)}`
1979
+ );
1980
+ }
1981
+ if (signalInfo.windows_fallback) {
1982
+ console.log(`
1983
+ Windows Fallback:`);
1984
+ console.log(` Log Level: ${signalInfo.windows_fallback.log_level}`);
1985
+ console.log(` Telemetry Event: ${signalInfo.windows_fallback.telemetry_event}`);
1986
+ console.log(` HTTP Operation: ${signalInfo.windows_fallback.operation_hint}`);
1987
+ }
1988
+ }
1989
+ } else {
1990
+ const signals = await listSignals();
1991
+ const version = await getSignalsVersion();
1992
+ if (cmdOptions?.json) {
1993
+ console.log(JSON.stringify({ version, signals }, null, 2));
1994
+ } else {
1995
+ console.log(`Signal Catalog Version: ${version}
1996
+ `);
1997
+ console.log(`Found ${signals.length} signal(s):
1998
+ `);
1999
+ const caps = await getPlatformCapabilities2();
2000
+ console.log(`Platform: ${caps.platform} (${caps.isPOSIX ? "POSIX" : "Windows"})`);
2001
+ console.log(
2002
+ `Signal Exit Codes: ${supportsSignalExitCodes2() ? "Supported" : "Not supported"}
2003
+ `
2004
+ );
2005
+ for (const sig of signals) {
2006
+ const supported = await supportsSignal(sig.name);
2007
+ const marker = supported ? "\u2713" : "\u2717";
2008
+ console.log(` ${marker} ${sig.name} (${sig.unix_number}): ${sig.description}`);
2009
+ }
2010
+ }
2011
+ }
2012
+ } catch (error) {
2013
+ console.error("Error showing signal info:", error.message);
2014
+ process.exit(exitCodes.EXIT_FAILURE);
2015
+ }
2016
+ });
2017
+ program.command("validate").description("Validate signal configuration file against schema").argument("<file>", "Signal configuration file to validate (YAML/JSON)").option("--json", "Output as JSON").action(async (file, cmdOptions) => {
2018
+ try {
2019
+ const content = await readFile(file, "utf-8");
2020
+ let data;
2021
+ if (file.endsWith(".json")) {
2022
+ data = JSON.parse(content);
2023
+ } else if (file.endsWith(".yaml") || file.endsWith(".yml")) {
2024
+ const yaml = await import('yaml');
2025
+ data = yaml.parse(content);
2026
+ } else {
2027
+ throw new Error("Unsupported file format. Use .json, .yaml, or .yml");
2028
+ }
2029
+ const { validateDataBySchemaId: validateDataBySchemaId2 } = await Promise.resolve().then(() => (init_validator(), validator_exports));
2030
+ const result = await validateDataBySchemaId2(
2031
+ "library/foundry/v1.0.0/signals",
2032
+ data
2033
+ );
2034
+ if (cmdOptions?.json) {
2035
+ console.log(JSON.stringify(result, null, 2));
2036
+ } else {
2037
+ if (result.valid) {
2038
+ console.log("\u2713 Validation passed");
2039
+ process.exit(exitCodes.EXIT_SUCCESS);
2040
+ } else {
2041
+ console.error("\u2717 Validation failed:\n");
2042
+ if (result.diagnostics) {
2043
+ for (const diag of result.diagnostics) {
2044
+ console.error(` ${diag.pointer}: ${diag.message}`);
2045
+ }
2046
+ }
2047
+ process.exit(exitCodes.EXIT_DATA_INVALID);
2048
+ }
2049
+ }
2050
+ } catch (error) {
2051
+ console.error("Error validating signal config:", error.message);
2052
+ process.exit(exitCodes.EXIT_FAILURE);
2053
+ }
2054
+ });
2055
+ program.command("platform").description("Show platform capabilities for signal handling").option("--json", "Output as JSON").action(async (cmdOptions) => {
2056
+ try {
2057
+ const caps = await getPlatformCapabilities2();
2058
+ const signalExitCodes = supportsSignalExitCodes2();
2059
+ if (cmdOptions?.json) {
2060
+ console.log(
2061
+ JSON.stringify(
2062
+ {
2063
+ ...caps,
2064
+ supportsSignalExitCodes: signalExitCodes
2065
+ },
2066
+ null,
2067
+ 2
2068
+ )
2069
+ );
2070
+ } else {
2071
+ console.log("Platform Capabilities:\n");
2072
+ console.log(` Platform: ${caps.platform}`);
2073
+ console.log(` POSIX: ${caps.isPOSIX ? "Yes" : "No"}`);
2074
+ console.log(` Windows: ${caps.isWindows ? "Yes" : "No"}`);
2075
+ console.log(` Signal Exit Codes: ${signalExitCodes ? "Supported" : "Not supported"}`);
2076
+ if (caps.supportedSignals && caps.supportedSignals.length > 0) {
2077
+ console.log("\n Supported Signals:");
2078
+ for (const signal of caps.supportedSignals) {
2079
+ console.log(` \u2713 ${signal}`);
2080
+ }
2081
+ }
2082
+ if (caps.unsupportedSignals && caps.unsupportedSignals.length > 0) {
2083
+ console.log("\n Unsupported Signals (use HTTP fallback):");
2084
+ for (const signal of caps.unsupportedSignals) {
2085
+ console.log(` \u2717 ${signal}`);
2086
+ }
2087
+ }
2088
+ }
2089
+ } catch (error) {
2090
+ console.error("Error getting platform capabilities:", error.message);
2091
+ process.exit(exitCodes.EXIT_FAILURE);
2092
+ }
2093
+ });
2094
+ return program;
2095
+ }
2096
+ async function main(argv) {
2097
+ const program = createSignalsCLI();
2098
+ await program.parseAsync(argv);
2099
+ }
2100
+
2101
+ // src/bin/signals-cli.ts
2102
+ void main();
2103
+ //# sourceMappingURL=signals-cli.js.map
2104
+ //# sourceMappingURL=signals-cli.js.map