@brutalist/mcp 1.8.0 → 1.9.0

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 (118) hide show
  1. package/README.md +26 -0
  2. package/dist/brutalist-server.d.ts +31 -9
  3. package/dist/brutalist-server.d.ts.map +1 -1
  4. package/dist/brutalist-server.js +107 -673
  5. package/dist/brutalist-server.js.map +1 -1
  6. package/dist/cli-adapters/claude-adapter.d.ts +25 -0
  7. package/dist/cli-adapters/claude-adapter.d.ts.map +1 -0
  8. package/dist/cli-adapters/claude-adapter.js +245 -0
  9. package/dist/cli-adapters/claude-adapter.js.map +1 -0
  10. package/dist/cli-adapters/codex-adapter.d.ts +23 -0
  11. package/dist/cli-adapters/codex-adapter.d.ts.map +1 -0
  12. package/dist/cli-adapters/codex-adapter.js +173 -0
  13. package/dist/cli-adapters/codex-adapter.js.map +1 -0
  14. package/dist/cli-adapters/gemini-adapter.d.ts +50 -0
  15. package/dist/cli-adapters/gemini-adapter.d.ts.map +1 -0
  16. package/dist/cli-adapters/gemini-adapter.js +196 -0
  17. package/dist/cli-adapters/gemini-adapter.js.map +1 -0
  18. package/dist/cli-adapters/index.d.ts +75 -0
  19. package/dist/cli-adapters/index.d.ts.map +1 -0
  20. package/dist/cli-adapters/index.js +29 -0
  21. package/dist/cli-adapters/index.js.map +1 -0
  22. package/dist/cli-adapters/shared.d.ts +12 -0
  23. package/dist/cli-adapters/shared.d.ts.map +1 -0
  24. package/dist/cli-adapters/shared.js +99 -0
  25. package/dist/cli-adapters/shared.js.map +1 -0
  26. package/dist/cli-agents.d.ts +64 -2
  27. package/dist/cli-agents.d.ts.map +1 -1
  28. package/dist/cli-agents.js +417 -401
  29. package/dist/cli-agents.js.map +1 -1
  30. package/dist/debate/constitutional.d.ts +27 -0
  31. package/dist/debate/constitutional.d.ts.map +1 -0
  32. package/dist/debate/constitutional.js +74 -0
  33. package/dist/debate/constitutional.js.map +1 -0
  34. package/dist/debate/debate-orchestrator.d.ts +154 -0
  35. package/dist/debate/debate-orchestrator.d.ts.map +1 -0
  36. package/dist/debate/debate-orchestrator.js +699 -0
  37. package/dist/debate/debate-orchestrator.js.map +1 -0
  38. package/dist/debate/index.d.ts +18 -0
  39. package/dist/debate/index.d.ts.map +1 -0
  40. package/dist/debate/index.js +18 -0
  41. package/dist/debate/index.js.map +1 -0
  42. package/dist/debate/refusal-detection.d.ts +27 -0
  43. package/dist/debate/refusal-detection.d.ts.map +1 -0
  44. package/dist/debate/refusal-detection.js +62 -0
  45. package/dist/debate/refusal-detection.js.map +1 -0
  46. package/dist/debate/synthesis.d.ts +22 -0
  47. package/dist/debate/synthesis.d.ts.map +1 -0
  48. package/dist/debate/synthesis.js +117 -0
  49. package/dist/debate/synthesis.js.map +1 -0
  50. package/dist/logger.d.ts +204 -1
  51. package/dist/logger.d.ts.map +1 -1
  52. package/dist/logger.js +398 -18
  53. package/dist/logger.js.map +1 -1
  54. package/dist/metrics/counter.d.ts +24 -0
  55. package/dist/metrics/counter.d.ts.map +1 -0
  56. package/dist/metrics/counter.js +60 -0
  57. package/dist/metrics/counter.js.map +1 -0
  58. package/dist/metrics/histogram.d.ts +42 -0
  59. package/dist/metrics/histogram.d.ts.map +1 -0
  60. package/dist/metrics/histogram.js +114 -0
  61. package/dist/metrics/histogram.js.map +1 -0
  62. package/dist/metrics/index.d.ts +26 -0
  63. package/dist/metrics/index.d.ts.map +1 -0
  64. package/dist/metrics/index.js +22 -0
  65. package/dist/metrics/index.js.map +1 -0
  66. package/dist/metrics/registry.d.ts +96 -0
  67. package/dist/metrics/registry.d.ts.map +1 -0
  68. package/dist/metrics/registry.js +113 -0
  69. package/dist/metrics/registry.js.map +1 -0
  70. package/dist/metrics/safe-metric.d.ts +25 -0
  71. package/dist/metrics/safe-metric.d.ts.map +1 -0
  72. package/dist/metrics/safe-metric.js +41 -0
  73. package/dist/metrics/safe-metric.js.map +1 -0
  74. package/dist/metrics/types.d.ts +82 -0
  75. package/dist/metrics/types.d.ts.map +1 -0
  76. package/dist/metrics/types.js +121 -0
  77. package/dist/metrics/types.js.map +1 -0
  78. package/dist/registry/argument-spaces.d.ts.map +1 -1
  79. package/dist/registry/argument-spaces.js +20 -0
  80. package/dist/registry/argument-spaces.js.map +1 -1
  81. package/dist/registry/domains.d.ts.map +1 -1
  82. package/dist/registry/domains.js +17 -1
  83. package/dist/registry/domains.js.map +1 -1
  84. package/dist/streaming/circuit-breaker.d.ts +13 -1
  85. package/dist/streaming/circuit-breaker.d.ts.map +1 -1
  86. package/dist/streaming/circuit-breaker.js +13 -1
  87. package/dist/streaming/circuit-breaker.js.map +1 -1
  88. package/dist/streaming/intelligent-buffer.d.ts +13 -1
  89. package/dist/streaming/intelligent-buffer.d.ts.map +1 -1
  90. package/dist/streaming/intelligent-buffer.js +13 -1
  91. package/dist/streaming/intelligent-buffer.js.map +1 -1
  92. package/dist/streaming/output-parser.d.ts +16 -2
  93. package/dist/streaming/output-parser.d.ts.map +1 -1
  94. package/dist/streaming/output-parser.js +16 -2
  95. package/dist/streaming/output-parser.js.map +1 -1
  96. package/dist/streaming/progress-tracker.d.ts +14 -1
  97. package/dist/streaming/progress-tracker.d.ts.map +1 -1
  98. package/dist/streaming/progress-tracker.js +14 -1
  99. package/dist/streaming/progress-tracker.js.map +1 -1
  100. package/dist/streaming/session-manager.d.ts +14 -1
  101. package/dist/streaming/session-manager.d.ts.map +1 -1
  102. package/dist/streaming/session-manager.js +14 -1
  103. package/dist/streaming/session-manager.js.map +1 -1
  104. package/dist/streaming/sse-transport.d.ts +12 -1
  105. package/dist/streaming/sse-transport.d.ts.map +1 -1
  106. package/dist/streaming/sse-transport.js +12 -1
  107. package/dist/streaming/sse-transport.js.map +1 -1
  108. package/dist/streaming/streaming-orchestrator.d.ts +15 -1
  109. package/dist/streaming/streaming-orchestrator.d.ts.map +1 -1
  110. package/dist/streaming/streaming-orchestrator.js +15 -1
  111. package/dist/streaming/streaming-orchestrator.js.map +1 -1
  112. package/dist/system-prompts.d.ts.map +1 -1
  113. package/dist/system-prompts.js +490 -4
  114. package/dist/system-prompts.js.map +1 -1
  115. package/dist/tool-definitions-generated.d.ts.map +1 -1
  116. package/dist/tool-definitions-generated.js +3 -1
  117. package/dist/tool-definitions-generated.js.map +1 -1
  118. package/package.json +1 -1
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Histogram — a cumulative histogram of observation values.
3
+ *
4
+ * The Prometheus convention: one `_bucket` series per upper bound plus a
5
+ * mandatory `+Inf` bucket, a `_count` series, and a `_sum` series. Buckets
6
+ * are CUMULATIVE — each bucket includes every lower bucket's observations.
7
+ *
8
+ * Buckets are declared once at construction. Per-label-set state includes:
9
+ * - counts[i] — cumulative count for the i-th bucket (counts[bucketsAscending.length] = +Inf).
10
+ * - sum — cumulative sum of observed values (for quantile approximation).
11
+ * - count — total observations (equals counts[+Inf bucket]).
12
+ */
13
+ import { escapeHelp, escapeLabelValue, serializeLabels } from './types.js';
14
+ export function createHistogram(descriptor) {
15
+ validateBuckets(descriptor);
16
+ const state = new Map();
17
+ return {
18
+ descriptor,
19
+ observe(labels, value) {
20
+ if (!Number.isFinite(value)) {
21
+ throw new Error(`metrics: histogram "${descriptor.name}" observation must be finite (got ${value})`);
22
+ }
23
+ // Symmetric with `Counter.inc()`: negative inputs are rejected by
24
+ // throwing. The registry's duration histogram measures wall-clock
25
+ // time, which is semantically non-negative; a bad delta (e.g.,
26
+ // `Date.now() - futureTimestamp`) would otherwise contaminate every
27
+ // finite bucket AND negatively shift `_sum`, silently breaking any
28
+ // downstream quantile or SLO computation.
29
+ if (value < 0) {
30
+ throw new Error(`metrics: histogram "${descriptor.name}" observation must be >= 0 (got ${value})`);
31
+ }
32
+ const key = serializeLabels(descriptor.labelNames, labels);
33
+ let entry = state.get(key);
34
+ if (!entry) {
35
+ entry = {
36
+ labels,
37
+ counts: new Array(descriptor.buckets.length + 1).fill(0),
38
+ sum: 0,
39
+ count: 0
40
+ };
41
+ state.set(key, entry);
42
+ }
43
+ // Cumulative: increment every bucket whose upper bound >= value.
44
+ for (let i = 0; i < descriptor.buckets.length; i++) {
45
+ if (value <= descriptor.buckets[i]) {
46
+ entry.counts[i] += 1;
47
+ }
48
+ }
49
+ // +Inf bucket always increments.
50
+ entry.counts[entry.counts.length - 1] += 1;
51
+ entry.sum += value;
52
+ entry.count += 1;
53
+ },
54
+ snapshot() {
55
+ const out = new Map();
56
+ for (const [key, entry] of state.entries()) {
57
+ out.set(key, {
58
+ cumulative: entry.counts.slice(),
59
+ sum: entry.sum,
60
+ count: entry.count
61
+ });
62
+ }
63
+ return out;
64
+ },
65
+ render() {
66
+ const lines = [];
67
+ lines.push(`# HELP ${descriptor.name} ${escapeHelp(descriptor.help)}`);
68
+ lines.push(`# TYPE ${descriptor.name} histogram`);
69
+ if (state.size === 0) {
70
+ return lines.join('\n');
71
+ }
72
+ const keys = Array.from(state.keys()).sort();
73
+ for (const key of keys) {
74
+ const entry = state.get(key);
75
+ const labelPrefix = renderLabelPrefix(key, descriptor.labelNames);
76
+ for (let i = 0; i < descriptor.buckets.length; i++) {
77
+ const bucket = descriptor.buckets[i];
78
+ lines.push(`${descriptor.name}_bucket{${labelPrefix}le="${escapeLabelValue(String(bucket))}"} ${entry.counts[i]}`);
79
+ }
80
+ // +Inf bucket — total count.
81
+ lines.push(`${descriptor.name}_bucket{${labelPrefix}le="+Inf"} ${entry.counts[entry.counts.length - 1]}`);
82
+ lines.push(`${descriptor.name}_sum${key === '' ? '' : `{${key}}`} ${formatNumber(entry.sum)}`);
83
+ lines.push(`${descriptor.name}_count${key === '' ? '' : `{${key}}`} ${entry.count}`);
84
+ }
85
+ return lines.join('\n');
86
+ }
87
+ };
88
+ }
89
+ function renderLabelPrefix(key, _labelNames) {
90
+ // `le` is always the final label in bucket lines; put the existing labels first.
91
+ return key === '' ? '' : `${key},`;
92
+ }
93
+ function formatNumber(value) {
94
+ if (Number.isInteger(value)) {
95
+ return value.toString();
96
+ }
97
+ return value.toString();
98
+ }
99
+ function validateBuckets(descriptor) {
100
+ if (descriptor.buckets.length === 0) {
101
+ throw new Error(`metrics: histogram "${descriptor.name}" must have at least one bucket`);
102
+ }
103
+ for (let i = 0; i < descriptor.buckets.length; i++) {
104
+ const b = descriptor.buckets[i];
105
+ if (!Number.isFinite(b)) {
106
+ throw new Error(`metrics: histogram "${descriptor.name}" bucket ${i} is not finite (${b})`);
107
+ }
108
+ if (i > 0 && b <= descriptor.buckets[i - 1]) {
109
+ throw new Error(`metrics: histogram "${descriptor.name}" buckets must be strictly ascending ` +
110
+ `(bucket ${i} = ${b} <= bucket ${i - 1} = ${descriptor.buckets[i - 1]})`);
111
+ }
112
+ }
113
+ }
114
+ //# sourceMappingURL=histogram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"histogram.js","sourceRoot":"","sources":["../../src/metrics/histogram.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Metrics module — barrel export.
3
+ *
4
+ * This is a TOOL, not wiring: nothing under `src/metrics/` imports from the
5
+ * debate, CLI-adapter, streaming, or logger modules. Call sites depend on
6
+ * this module; this module depends on nothing inside the project.
7
+ *
8
+ * Usage (composition root):
9
+ *
10
+ * import { createMetricsRegistry } from './metrics/index.js';
11
+ * const metrics = createMetricsRegistry();
12
+ * // ... pass `metrics` into DebateOrchestratorDeps, CLIAgentOrchestrator, etc.
13
+ *
14
+ * Usage (test):
15
+ *
16
+ * const metrics = createMetricsRegistry();
17
+ * metrics.cliSpawnTotal.inc({ provider: 'claude', outcome: 'success' });
18
+ * expect(metrics.getMetricsAsText()).toContain('brutalist_cli_spawn_total');
19
+ */
20
+ export { createMetricsRegistry, PROMETHEUS_CONTENT_TYPE, DEBATE_DURATION_LABELS, DEBATE_DURATION_BUCKETS, ESCALATION_TIER_LABELS, CLI_SPAWN_LABELS, STREAMING_EVENT_LABELS } from './registry.js';
21
+ export type { MetricsRegistry } from './registry.js';
22
+ export type { Counter } from './counter.js';
23
+ export type { Histogram, HistogramDescriptor, HistogramSnapshot } from './histogram.js';
24
+ export type { LabelValues, MetricDescriptor, LabelKey } from './types.js';
25
+ export { safeMetric } from './safe-metric.js';
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExF,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE1E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Metrics module — barrel export.
3
+ *
4
+ * This is a TOOL, not wiring: nothing under `src/metrics/` imports from the
5
+ * debate, CLI-adapter, streaming, or logger modules. Call sites depend on
6
+ * this module; this module depends on nothing inside the project.
7
+ *
8
+ * Usage (composition root):
9
+ *
10
+ * import { createMetricsRegistry } from './metrics/index.js';
11
+ * const metrics = createMetricsRegistry();
12
+ * // ... pass `metrics` into DebateOrchestratorDeps, CLIAgentOrchestrator, etc.
13
+ *
14
+ * Usage (test):
15
+ *
16
+ * const metrics = createMetricsRegistry();
17
+ * metrics.cliSpawnTotal.inc({ provider: 'claude', outcome: 'success' });
18
+ * expect(metrics.getMetricsAsText()).toContain('brutalist_cli_spawn_total');
19
+ */
20
+ export { createMetricsRegistry, PROMETHEUS_CONTENT_TYPE, DEBATE_DURATION_LABELS, DEBATE_DURATION_BUCKETS, ESCALATION_TIER_LABELS, CLI_SPAWN_LABELS, STREAMING_EVENT_LABELS } from './registry.js';
21
+ export { safeMetric } from './safe-metric.js';
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,eAAe,CAAC;AASvB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * MetricsRegistry — the DI-friendly container for the four required metric
3
+ * surfaces (debate duration, escalation tier, CLI spawn, streaming events).
4
+ *
5
+ * `createMetricsRegistry()` is a FACTORY, not a singleton accessor. Two calls
6
+ * produce two completely independent registries — this is the property that
7
+ * makes tests in the consuming modules reliable: each test can construct a
8
+ * fresh registry and assert on its state without interference from other
9
+ * tests or from module-level state.
10
+ *
11
+ * Critical invariants (enforced by tests):
12
+ * - No module-level `new` at import time.
13
+ * - No environment-variable reads at import time.
14
+ * - Factory is idempotent per call site: `createMetricsRegistry() !== createMetricsRegistry()`.
15
+ * - `getMetricsAsText()` emits a valid Prometheus text-format 0.0.4 exposition.
16
+ */
17
+ import type { Counter } from './counter.js';
18
+ import type { Histogram } from './histogram.js';
19
+ /**
20
+ * Labels for the debate orchestration duration histogram.
21
+ * - outcome: `success` | `refused` | `error` (success means a non-refused
22
+ * debate completed end-to-end; refused captures the constitutional refusal
23
+ * path; error captures thrown/uncaught failures.)
24
+ * - tier: the escalation tier at which the debate resolved; matches
25
+ * `DebateTier` from `src/debate/constitutional.ts` — values:
26
+ * `standard` | `escalated` | `decomposed`.
27
+ */
28
+ export declare const DEBATE_DURATION_LABELS: readonly ["outcome", "tier"];
29
+ /** Labels for the escalation tier counter. Tier values as above. */
30
+ export declare const ESCALATION_TIER_LABELS: readonly ["tier"];
31
+ /**
32
+ * Labels for CLI spawn outcomes.
33
+ * - provider: `claude` | `codex` | `gemini`.
34
+ * - outcome: `success` | `failure` | `timeout` | `refused`.
35
+ * (Integration phase chooses the exact outcome; the metric accepts any
36
+ * string but conventions SHOULD stick to the four above for consistent
37
+ * PromQL grouping.)
38
+ */
39
+ export declare const CLI_SPAWN_LABELS: readonly ["provider", "outcome"];
40
+ /**
41
+ * Labels for streaming events.
42
+ * - transport: `stdio` | `http` — the two canonical MCP transports, per
43
+ * `src/streaming/STREAMING_ARCHITECTURE.md`.
44
+ * - event_type: `agent_progress` | `agent_error` | `progress_update` | ...
45
+ * matches `StreamingEvent.type` from `src/cli-agents.ts` conventions.
46
+ */
47
+ export declare const STREAMING_EVENT_LABELS: readonly ["transport", "event_type"];
48
+ /**
49
+ * Histogram buckets for debate durations, in seconds.
50
+ *
51
+ * Debates spawn multiple CLI agents (Claude/Codex/Gemini) and run 2-3 rounds;
52
+ * total latency ranges from ~seconds (cached path) to minutes (full 3-tier
53
+ * escalation with a cold start). These buckets give sensible resolution across
54
+ * that full range while keeping cardinality low enough for Prometheus storage.
55
+ */
56
+ export declare const DEBATE_DURATION_BUCKETS: readonly number[];
57
+ /**
58
+ * The metrics surface exported by a registry.
59
+ *
60
+ * Each property is a ready-to-use metric handle; instrumentation code only
61
+ * needs to call `inc()` / `observe()` and never sees the underlying registry.
62
+ */
63
+ export interface MetricsRegistry {
64
+ /** Histogram: debate orchestration duration, seconds, labeled by outcome & tier. */
65
+ readonly debateOrchestrationDurationSeconds: Histogram<typeof DEBATE_DURATION_LABELS>;
66
+ /** Counter: total debates per escalation tier reached. */
67
+ readonly debateEscalationTierTotal: Counter<typeof ESCALATION_TIER_LABELS>;
68
+ /** Counter: CLI spawn attempts partitioned by provider and outcome. */
69
+ readonly cliSpawnTotal: Counter<typeof CLI_SPAWN_LABELS>;
70
+ /** Counter: streaming events dispatched per transport and event type. */
71
+ readonly streamingEventsTotal: Counter<typeof STREAMING_EVENT_LABELS>;
72
+ /**
73
+ * Render the full registry as a Prometheus text-format 0.0.4 exposition.
74
+ *
75
+ * Content-Type: `text/plain; version=0.0.4` (the constant is exported
76
+ * separately as `PROMETHEUS_CONTENT_TYPE` for the optional HTTP exposure
77
+ * that the integration phase may add).
78
+ */
79
+ getMetricsAsText(): string;
80
+ }
81
+ /**
82
+ * The Prometheus text exposition Content-Type.
83
+ *
84
+ * Per the 0.0.4 spec: `text/plain; version=0.0.4; charset=utf-8`.
85
+ */
86
+ export declare const PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
87
+ /**
88
+ * Construct a fresh, independent metrics registry.
89
+ *
90
+ * Zero arguments keeps the signature minimal for composition-root wiring;
91
+ * customisation (buckets, label sets) is done by editing this file rather
92
+ * than exposing constructor knobs — this keeps every consumer's metric
93
+ * surface identical across test and production builds.
94
+ */
95
+ export declare function createMetricsRegistry(): MetricsRegistry;
96
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/metrics/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAIhD;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,8BAA+B,CAAC;AAEnE,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,mBAAoB,CAAC;AAExD;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,kCAAmC,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,sCAAuC,CAAC;AAE3E;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAEpD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,oFAAoF;IACpF,QAAQ,CAAC,kCAAkC,EAAE,SAAS,CAAC,OAAO,sBAAsB,CAAC,CAAC;IACtF,0DAA0D;IAC1D,QAAQ,CAAC,yBAAyB,EAAE,OAAO,CAAC,OAAO,sBAAsB,CAAC,CAAC;IAC3E,uEAAuE;IACvE,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,gBAAgB,CAAC,CAAC;IACzD,yEAAyE;IACzE,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC,OAAO,sBAAsB,CAAC,CAAC;IAEtE;;;;;;OAMG;IACH,gBAAgB,IAAI,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,6CAA6C,CAAC;AAElF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CA2CvD"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * MetricsRegistry — the DI-friendly container for the four required metric
3
+ * surfaces (debate duration, escalation tier, CLI spawn, streaming events).
4
+ *
5
+ * `createMetricsRegistry()` is a FACTORY, not a singleton accessor. Two calls
6
+ * produce two completely independent registries — this is the property that
7
+ * makes tests in the consuming modules reliable: each test can construct a
8
+ * fresh registry and assert on its state without interference from other
9
+ * tests or from module-level state.
10
+ *
11
+ * Critical invariants (enforced by tests):
12
+ * - No module-level `new` at import time.
13
+ * - No environment-variable reads at import time.
14
+ * - Factory is idempotent per call site: `createMetricsRegistry() !== createMetricsRegistry()`.
15
+ * - `getMetricsAsText()` emits a valid Prometheus text-format 0.0.4 exposition.
16
+ */
17
+ import { createCounter } from './counter.js';
18
+ import { createHistogram } from './histogram.js';
19
+ /**
20
+ * Labels for the debate orchestration duration histogram.
21
+ * - outcome: `success` | `refused` | `error` (success means a non-refused
22
+ * debate completed end-to-end; refused captures the constitutional refusal
23
+ * path; error captures thrown/uncaught failures.)
24
+ * - tier: the escalation tier at which the debate resolved; matches
25
+ * `DebateTier` from `src/debate/constitutional.ts` — values:
26
+ * `standard` | `escalated` | `decomposed`.
27
+ */
28
+ export const DEBATE_DURATION_LABELS = ['outcome', 'tier'];
29
+ /** Labels for the escalation tier counter. Tier values as above. */
30
+ export const ESCALATION_TIER_LABELS = ['tier'];
31
+ /**
32
+ * Labels for CLI spawn outcomes.
33
+ * - provider: `claude` | `codex` | `gemini`.
34
+ * - outcome: `success` | `failure` | `timeout` | `refused`.
35
+ * (Integration phase chooses the exact outcome; the metric accepts any
36
+ * string but conventions SHOULD stick to the four above for consistent
37
+ * PromQL grouping.)
38
+ */
39
+ export const CLI_SPAWN_LABELS = ['provider', 'outcome'];
40
+ /**
41
+ * Labels for streaming events.
42
+ * - transport: `stdio` | `http` — the two canonical MCP transports, per
43
+ * `src/streaming/STREAMING_ARCHITECTURE.md`.
44
+ * - event_type: `agent_progress` | `agent_error` | `progress_update` | ...
45
+ * matches `StreamingEvent.type` from `src/cli-agents.ts` conventions.
46
+ */
47
+ export const STREAMING_EVENT_LABELS = ['transport', 'event_type'];
48
+ /**
49
+ * Histogram buckets for debate durations, in seconds.
50
+ *
51
+ * Debates spawn multiple CLI agents (Claude/Codex/Gemini) and run 2-3 rounds;
52
+ * total latency ranges from ~seconds (cached path) to minutes (full 3-tier
53
+ * escalation with a cold start). These buckets give sensible resolution across
54
+ * that full range while keeping cardinality low enough for Prometheus storage.
55
+ */
56
+ export const DEBATE_DURATION_BUCKETS = [
57
+ 0.5, 1, 2, 5, 10, 30, 60, 120, 300
58
+ ];
59
+ /**
60
+ * The Prometheus text exposition Content-Type.
61
+ *
62
+ * Per the 0.0.4 spec: `text/plain; version=0.0.4; charset=utf-8`.
63
+ */
64
+ export const PROMETHEUS_CONTENT_TYPE = 'text/plain; version=0.0.4; charset=utf-8';
65
+ /**
66
+ * Construct a fresh, independent metrics registry.
67
+ *
68
+ * Zero arguments keeps the signature minimal for composition-root wiring;
69
+ * customisation (buckets, label sets) is done by editing this file rather
70
+ * than exposing constructor knobs — this keeps every consumer's metric
71
+ * surface identical across test and production builds.
72
+ */
73
+ export function createMetricsRegistry() {
74
+ const debateOrchestrationDurationSeconds = createHistogram({
75
+ name: 'brutalist_debate_orchestration_duration_seconds',
76
+ help: 'Wall-clock duration of a CLI debate orchestration from start to finish, in seconds.',
77
+ labelNames: DEBATE_DURATION_LABELS,
78
+ buckets: DEBATE_DURATION_BUCKETS
79
+ });
80
+ const debateEscalationTierTotal = createCounter({
81
+ name: 'brutalist_debate_escalation_tier_total',
82
+ help: 'Total debates that reached a given escalation tier (standard/escalated/decomposed).',
83
+ labelNames: ESCALATION_TIER_LABELS
84
+ });
85
+ const cliSpawnTotal = createCounter({
86
+ name: 'brutalist_cli_spawn_total',
87
+ help: 'Total CLI agent spawn attempts partitioned by provider and outcome.',
88
+ labelNames: CLI_SPAWN_LABELS
89
+ });
90
+ const streamingEventsTotal = createCounter({
91
+ name: 'brutalist_streaming_events_total',
92
+ help: 'Total streaming events dispatched, labeled by transport and event type.',
93
+ labelNames: STREAMING_EVENT_LABELS
94
+ });
95
+ return {
96
+ debateOrchestrationDurationSeconds,
97
+ debateEscalationTierTotal,
98
+ cliSpawnTotal,
99
+ streamingEventsTotal,
100
+ getMetricsAsText() {
101
+ // Blocks are separated by a single blank line; the final block ends
102
+ // with a trailing newline per the text format 0.0.4 convention.
103
+ const blocks = [
104
+ debateOrchestrationDurationSeconds.render(),
105
+ debateEscalationTierTotal.render(),
106
+ cliSpawnTotal.render(),
107
+ streamingEventsTotal.render()
108
+ ];
109
+ return blocks.join('\n') + '\n';
110
+ }
111
+ };
112
+ }
113
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/metrics/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,SAAS,EAAE,MAAM,CAAU,CAAC;AAEnE,oEAAoE;AACpE,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,MAAM,CAAU,CAAC;AAExD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,UAAU,EAAE,SAAS,CAAU,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,WAAW,EAAE,YAAY,CAAU,CAAC;AAE3E;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAsB;IACxD,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG;CACnC,CAAC;AA4BF;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,0CAA0C,CAAC;AAElF;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,kCAAkC,GAAG,eAAe,CAAC;QACzD,IAAI,EAAE,iDAAiD;QACvD,IAAI,EAAE,qFAAqF;QAC3F,UAAU,EAAE,sBAAsB;QAClC,OAAO,EAAE,uBAAuB;KACjC,CAAC,CAAC;IAEH,MAAM,yBAAyB,GAAG,aAAa,CAAC;QAC9C,IAAI,EAAE,wCAAwC;QAC9C,IAAI,EAAE,qFAAqF;QAC3F,UAAU,EAAE,sBAAsB;KACnC,CAAC,CAAC;IAEH,MAAM,aAAa,GAAG,aAAa,CAAC;QAClC,IAAI,EAAE,2BAA2B;QACjC,IAAI,EAAE,qEAAqE;QAC3E,UAAU,EAAE,gBAAgB;KAC7B,CAAC,CAAC;IAEH,MAAM,oBAAoB,GAAG,aAAa,CAAC;QACzC,IAAI,EAAE,kCAAkC;QACxC,IAAI,EAAE,yEAAyE;QAC/E,UAAU,EAAE,sBAAsB;KACnC,CAAC,CAAC;IAEH,OAAO;QACL,kCAAkC;QAClC,yBAAyB;QACzB,aAAa;QACb,oBAAoB;QACpB,gBAAgB;YACd,oEAAoE;YACpE,gEAAgE;YAChE,MAAM,MAAM,GAAG;gBACb,kCAAkC,CAAC,MAAM,EAAE;gBAC3C,yBAAyB,CAAC,MAAM,EAAE;gBAClC,aAAa,CAAC,MAAM,EAAE;gBACtB,oBAAoB,CAAC,MAAM,EAAE;aAC9B,CAAC;YACF,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QAClC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * safeMetric — isolate metric writes from business control flow.
3
+ *
4
+ * Metric emissions are side effects that MUST NOT propagate exceptions
5
+ * into the surrounding business logic. If a metric throw escaped into
6
+ * an outer try/catch (e.g., the per-turn try/catch in
7
+ * DebateOrchestrator.executeCLIDebate or the spawn try/catch in
8
+ * CLIAgentOrchestrator._executeCLI), the business layer would treat
9
+ * the metric failure as an operational failure — double-counting,
10
+ * double-pushing metadata, potentially re-emitting streaming events.
11
+ *
12
+ * This helper wraps each metric call in a local try/catch that swallows
13
+ * the exception and emits a warn-level log bound to
14
+ * `operation='metrics'`. The hot-path no-I/O invariant is preserved:
15
+ * `.warn` only fires on the rare exception path (contract-violating
16
+ * label input or registry misconfiguration), and no synchronous I/O is
17
+ * added on the happy path.
18
+ *
19
+ * This module is a TOOL — it imports only the logger type and has no
20
+ * other project dependencies. Call sites (debate, cli spawn) own the
21
+ * operation label.
22
+ */
23
+ import type { StructuredLogger } from '../logger.js';
24
+ export declare function safeMetric(log: StructuredLogger, op: string, fn: () => void): void;
25
+ //# sourceMappingURL=safe-metric.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-metric.d.ts","sourceRoot":"","sources":["../../src/metrics/safe-metric.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,wBAAgB,UAAU,CACxB,GAAG,EAAE,gBAAgB,EACrB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,IAAI,GACb,IAAI,CAgBN"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * safeMetric — isolate metric writes from business control flow.
3
+ *
4
+ * Metric emissions are side effects that MUST NOT propagate exceptions
5
+ * into the surrounding business logic. If a metric throw escaped into
6
+ * an outer try/catch (e.g., the per-turn try/catch in
7
+ * DebateOrchestrator.executeCLIDebate or the spawn try/catch in
8
+ * CLIAgentOrchestrator._executeCLI), the business layer would treat
9
+ * the metric failure as an operational failure — double-counting,
10
+ * double-pushing metadata, potentially re-emitting streaming events.
11
+ *
12
+ * This helper wraps each metric call in a local try/catch that swallows
13
+ * the exception and emits a warn-level log bound to
14
+ * `operation='metrics'`. The hot-path no-I/O invariant is preserved:
15
+ * `.warn` only fires on the rare exception path (contract-violating
16
+ * label input or registry misconfiguration), and no synchronous I/O is
17
+ * added on the happy path.
18
+ *
19
+ * This module is a TOOL — it imports only the logger type and has no
20
+ * other project dependencies. Call sites (debate, cli spawn) own the
21
+ * operation label.
22
+ */
23
+ export function safeMetric(log, op, fn) {
24
+ try {
25
+ fn();
26
+ }
27
+ catch (err) {
28
+ // Security (Cycle 3 F36): the MetricsRegistry (Counter/Histogram)
29
+ // only throws synthetic label-validation errors with static strings
30
+ // today, but defense-in-depth says a future caller that passes
31
+ // user-controlled label values must not leak through this sink.
32
+ // Emit class-name only (err.name) — no payload. Do NOT add a
33
+ // nested try/catch around this warn call; F27/F28/F29 (logger-
34
+ // throw propagation) is suppressed by the convergence gate
35
+ // (see decisions.md Cycle 2 ASSESS).
36
+ log.forOperation('metrics').warn(`metric ${op} failed`, {
37
+ err: err instanceof Error ? (err.name ?? 'Error') : '<non-Error-thrown>',
38
+ });
39
+ }
40
+ }
41
+ //# sourceMappingURL=safe-metric.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-metric.js","sourceRoot":"","sources":["../../src/metrics/safe-metric.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,MAAM,UAAU,UAAU,CACxB,GAAqB,EACrB,EAAU,EACV,EAAc;IAEd,IAAI,CAAC;QACH,EAAE,EAAE,CAAC;IACP,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,kEAAkE;QAClE,oEAAoE;QACpE,+DAA+D;QAC/D,gEAAgE;QAChE,6DAA6D;QAC7D,+DAA+D;QAC/D,2DAA2D;QAC3D,qCAAqC;QACrC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE;YACtD,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,oBAAoB;SACzE,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Shared type primitives for the metrics module.
3
+ *
4
+ * `LabelValues` intentionally constrains label names at the type level: each
5
+ * metric declares a tuple of its label keys and consumers must supply every
6
+ * key — this catches missing-label bugs at compile time rather than exposing
7
+ * an untyped `Record<string, string>` to callers.
8
+ */
9
+ /** Cardinality-safe label value map. Values are stringified before emission. */
10
+ export type LabelValues<TLabels extends readonly string[]> = {
11
+ [K in TLabels[number]]: string | number;
12
+ };
13
+ /** Common metadata carried by every declared metric. */
14
+ export interface MetricDescriptor<TLabels extends readonly string[]> {
15
+ /** Fully-qualified Prometheus metric name (e.g. `brutalist_debate_...`). */
16
+ readonly name: string;
17
+ /** One-line HELP text emitted in the exposition. */
18
+ readonly help: string;
19
+ /** The ordered label key tuple. */
20
+ readonly labelNames: TLabels;
21
+ }
22
+ /**
23
+ * The internal storage shape shared by Counter and Histogram.
24
+ * A metric keeps a map from `labelKey` (stable, deterministic serialization
25
+ * of its label values) to per-label-set state.
26
+ */
27
+ export type LabelKey = string;
28
+ /**
29
+ * Build a stable, deterministic label key from a label-value map.
30
+ *
31
+ * Labels are emitted in the order declared in `labelNames` so two equivalent
32
+ * maps always produce the same key regardless of call-site iteration order.
33
+ *
34
+ * Values are coerced to strings and newline/quote-escaped for safe embedding
35
+ * in the exposition text format.
36
+ */
37
+ export declare function serializeLabels<TLabels extends readonly string[]>(labelNames: TLabels, values: LabelValues<TLabels>): LabelKey;
38
+ /**
39
+ * Escape a label value for the Prometheus text format 0.0.4 exposition.
40
+ *
41
+ * The spec only mandates escaping of three characters inside a label value:
42
+ * \\ -> \\\\ (backslash — must come FIRST so later-emitted escape
43
+ * sequences that include a literal `\` do not get their
44
+ * backslash doubled on a second pass. The single-pass
45
+ * character-walker below makes the order moot in practice
46
+ * because each input char is transformed exactly once,
47
+ * but we keep the documented ordering for clarity and
48
+ * parity with any future two-pass rewrite.)
49
+ * " -> \\" (double quote — else the label value terminator is
50
+ * ambiguous and a crafted input can forge a label pair.)
51
+ * \n -> \\n (LF — else a newline injects a fake metric line.)
52
+ *
53
+ * We ALSO escape CR (`\r`) even though the 0.0.4 spec does not strictly
54
+ * require it: without CR escaping an input like `x\r\nmalicious_metric 1`
55
+ * ends the current line in any CRLF-aware parser (HTTP scrapers, browser
56
+ * views, log viewers) and forges a downstream metric line. The future
57
+ * integrate_observability phase may add HTTP exposition; escaping CR now
58
+ * removes a whole class of injection vectors before anyone exposes the
59
+ * exposition to untrusted network paths.
60
+ *
61
+ * Other control characters (C0 range 0x00-0x1F, DEL 0x7F, NUL) are passed
62
+ * through as-is. Rationale: (a) none of them can terminate a label value
63
+ * or inject a line break in the 0.0.4 grammar; (b) metric callers in this
64
+ * codebase build label values from closed sets (`claude`/`codex`/`gemini`,
65
+ * `success`/`failure`, etc.) so there is no realistic path for a NUL or
66
+ * bell character to reach this function; (c) stripping/rejecting them is
67
+ * policy, not encoding correctness — the integrate_observability phase can
68
+ * add a reject/sanitize layer at the call site if that policy is needed.
69
+ */
70
+ export declare function escapeLabelValue(value: string): string;
71
+ /**
72
+ * Escape a HELP text per the Prometheus text format 0.0.4 spec:
73
+ * \\ -> \\\\
74
+ * \n -> \\n
75
+ * (quote escaping is NOT required in HELP lines, only in label values.)
76
+ *
77
+ * CR is also escaped — same rationale as `escapeLabelValue`: downstream
78
+ * HTTP/browser tooling is CRLF-aware and an unescaped CR would forge
79
+ * subsequent lines in the exposition output.
80
+ */
81
+ export declare function escapeHelp(value: string): string;
82
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/metrics/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gFAAgF;AAChF,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,SAAS,MAAM,EAAE,IAAI;KAC1D,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM;CACxC,CAAC;AAEF,wDAAwD;AACxD,MAAM,WAAW,gBAAgB,CAAC,OAAO,SAAS,SAAS,MAAM,EAAE;IACjE,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,MAAM,EAAE,EAC/D,UAAU,EAAE,OAAO,EACnB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,GAC3B,QAAQ,CAeV;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAuBtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAehD"}