@a-company/paradigm 3.1.6 → 3.5.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 (80) hide show
  1. package/dist/{accept-orchestration-CWZNCGZX.js → accept-orchestration-DIGPJVUR.js} +6 -5
  2. package/dist/{aggregate-W7Q6VIM2.js → aggregate-V4KPR3RW.js} +2 -2
  3. package/dist/{beacon-B47XSTL7.js → beacon-XRXL5KZB.js} +2 -2
  4. package/dist/{chunk-4LGLU2LO.js → chunk-2E2RTBSM.js} +533 -182
  5. package/dist/{chunk-YCLN7WXV.js → chunk-2QNZ6PVD.js} +219 -35
  6. package/dist/{chunk-UM54F7G5.js → chunk-4N6AYEEA.js} +1 -1
  7. package/dist/{chunk-MVXJVRFI.js → chunk-5TUAVVIG.js} +65 -1
  8. package/dist/{chunk-5C4SGQKH.js → chunk-6P4IFIK2.js} +4 -2
  9. package/dist/{chunk-WS5KM7OL.js → chunk-6RNYVBSG.js} +1 -1
  10. package/dist/{chunk-N6PJAPDE.js → chunk-AK5M6KJB.js} +18 -0
  11. package/dist/{chunk-VZ7CXFRZ.js → chunk-CRICL4FQ.js} +1004 -17
  12. package/dist/{chunk-MC7XC7XQ.js → chunk-GZDFVP2N.js} +20 -13
  13. package/dist/chunk-HPC3JAUP.js +42 -0
  14. package/dist/chunk-IRVA7NKV.js +657 -0
  15. package/dist/{chunk-ZPN7MXRA.js → chunk-KFHK6EBI.js} +184 -1
  16. package/dist/{chunk-UUZ2DMG5.js → chunk-KWDTBXP2.js} +1 -1
  17. package/dist/{chunk-HXY6AY52.js → chunk-M2XMTJHQ.js} +667 -70
  18. package/dist/{chunk-PW2EXJQT.js → chunk-MRENOFTR.js} +24 -1
  19. package/dist/{chunk-QS36NGWV.js → chunk-QHJGB5TV.js} +1 -1
  20. package/dist/chunk-UI3XXVJ6.js +449 -0
  21. package/dist/{chunk-AD2LSCHB.js → chunk-Y4XZWCHK.js} +40 -74
  22. package/dist/{constellation-K3CIQCHI.js → constellation-GNK5DIMH.js} +2 -2
  23. package/dist/{cost-AEK6R7HK.js → cost-AGO5N7DD.js} +1 -1
  24. package/dist/{cursorrules-KI5QWHIX.js → cursorrules-LQFA7M62.js} +2 -2
  25. package/dist/{delete-W67IVTLJ.js → delete-3YXAJ5AA.js} +12 -1
  26. package/dist/{diff-AJJ5H6HV.js → diff-J6C5IHPV.js} +6 -5
  27. package/dist/{dist-2F7NO4H4-KSL6SJIO.js → dist-AG5JNIZU-XSEZ2LLK.js} +28 -3
  28. package/dist/dist-JOHRYQUA.js +7294 -0
  29. package/dist/{dist-NHJQVVUW.js → dist-Q6SAZI7X.js} +2 -2
  30. package/dist/{dist-GPQ4LAY3.js → dist-YP2CO4TG.js} +24 -6
  31. package/dist/{doctor-JBIV5PMN.js → doctor-TQYRF7KK.js} +2 -2
  32. package/dist/{edit-Y7XPYSMK.js → edit-EOMPXOG5.js} +1 -1
  33. package/dist/flow-7JUH6D4H.js +185 -0
  34. package/dist/global-AXILUM5X.js +136 -0
  35. package/dist/{habits-FA65W77Y.js → habits-CHP4EW5H.js} +234 -5
  36. package/dist/{hooks-RLJFGKPF.js → hooks-DLZEYHI3.js} +1 -1
  37. package/dist/index.js +125 -100
  38. package/dist/{lint-HXKTWRNO.js → lint-N4LMMEXH.js} +141 -1
  39. package/dist/{list-R3QWW4SC.js → list-JKBJ7ESH.js} +1 -1
  40. package/dist/mcp.js +9273 -6515
  41. package/dist/{orchestrate-4ZH5GUQH.js → orchestrate-FAV64G2R.js} +6 -5
  42. package/dist/{probe-OYCP4JYG.js → probe-X3J2JX62.js} +18 -3
  43. package/dist/{promote-E6NBZ3BK.js → promote-HZH5E5CO.js} +1 -1
  44. package/dist/{providers-4PGPZEWP.js → providers-NQ67LO2Z.js} +1 -1
  45. package/dist/{record-OHQNWOUP.js → record-EECZ3E4I.js} +1 -1
  46. package/dist/{remember-6VZ74B7E.js → remember-3KJZGDUG.js} +1 -1
  47. package/dist/{review-RUHX25A5.js → review-BF26ILZB.js} +1 -1
  48. package/dist/{ripple-SBQOSTZD.js → ripple-JIUAMBLA.js} +2 -2
  49. package/dist/sentinel-ZTL224IG.js +63 -0
  50. package/dist/{server-MV4HNFVF.js → server-MZBYDXJY.js} +4193 -9
  51. package/dist/{setup-DF4F3ICN.js → setup-363IB6MO.js} +1 -1
  52. package/dist/{setup-JHBPZAG7.js → setup-UKJ3VGHI.js} +4 -4
  53. package/dist/{shift-YELZUPYG.js → shift-KDVYB6CR.js} +16 -13
  54. package/dist/{show-WTOJXUTN.js → show-SAMTXEHG.js} +1 -1
  55. package/dist/{snapshot-GTVPRYZG.js → snapshot-KCMONZAO.js} +2 -2
  56. package/dist/{spawn-BJRQA2NR.js → spawn-EO7B2UM3.js} +2 -2
  57. package/dist/{summary-5SBFO7QK.js → summary-E2PU4UN2.js} +3 -3
  58. package/dist/{switch-6EANJ7O6.js → switch-CC2KACXO.js} +1 -1
  59. package/dist/{sync-5KSTPJ4B.js → sync-5VJPZQNX.js} +2 -2
  60. package/dist/sync-llms-7QDA3ZWC.js +166 -0
  61. package/dist/{team-NWP2KJAB.js → team-6CCNANKE.js} +7 -6
  62. package/dist/{test-MA5TWJQV.js → test-DK2RWLTK.js} +91 -8
  63. package/dist/{thread-JCJVRUQR.js → thread-RNSLADXN.js} +18 -2
  64. package/dist/{timeline-P7BARFLI.js → timeline-TJDVVVA3.js} +1 -1
  65. package/dist/{triage-TBIWJA6R.js → triage-PXMU3RWV.js} +2 -2
  66. package/dist/university-content/courses/para-101.json +2 -1
  67. package/dist/university-content/courses/para-201.json +102 -3
  68. package/dist/university-content/courses/para-301.json +14 -11
  69. package/dist/university-content/courses/para-401.json +57 -3
  70. package/dist/university-content/courses/para-501.json +204 -6
  71. package/dist/university-content/plsat/v3.0.json +808 -3
  72. package/dist/university-content/reference.json +270 -0
  73. package/dist/{upgrade-TIYFQYPO.js → upgrade-RBSE4M6I.js} +1 -1
  74. package/dist/{validate-QEEY6KFS.js → validate-2LTHHORX.js} +1 -1
  75. package/dist/{watch-4LT4O6K7.js → watch-NBPOMOEX.js} +76 -0
  76. package/dist/{watch-2XEYUH43.js → watch-PAEH6MOG.js} +1 -1
  77. package/package.json +1 -1
  78. package/dist/chunk-GWM2WRXL.js +0 -1095
  79. package/dist/sentinel-WB7GIK4V.js +0 -43
  80. /package/dist/{chunk-TAP5N3HH.js → chunk-CCG6KYBT.js} +0 -0
@@ -1,7 +1,420 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- SentinelStorage
4
- } from "./chunk-VZ7CXFRZ.js";
3
+ SentinelStorage,
4
+ v4_default
5
+ } from "./chunk-CRICL4FQ.js";
6
+
7
+ // ../sentinel/dist/chunk-VQ3SIN7S.js
8
+ var DEFAULTS = {
9
+ url: "http://localhost:3838",
10
+ batchSize: 50,
11
+ flushIntervalMs: 5e3,
12
+ maxBufferSize: 1e3,
13
+ maxRetries: 3,
14
+ retryBackoffMs: 1e3
15
+ };
16
+ var SentinelClient = class {
17
+ url;
18
+ service;
19
+ version;
20
+ environment;
21
+ token;
22
+ batchSize;
23
+ maxBufferSize;
24
+ maxRetries;
25
+ retryBackoffMs;
26
+ onDrop;
27
+ onError;
28
+ sessionId;
29
+ logBuffer = [];
30
+ metricsBuffer = [];
31
+ flushTimer = null;
32
+ closed = false;
33
+ beforeUnloadHandler = null;
34
+ constructor(options) {
35
+ this.url = (options.url ?? DEFAULTS.url).replace(/\/+$/, "");
36
+ this.service = options.service;
37
+ this.version = options.version;
38
+ this.environment = options.environment;
39
+ this.token = options.token;
40
+ this.batchSize = options.batchSize ?? DEFAULTS.batchSize;
41
+ this.maxBufferSize = options.maxBufferSize ?? DEFAULTS.maxBufferSize;
42
+ this.maxRetries = options.maxRetries ?? DEFAULTS.maxRetries;
43
+ this.retryBackoffMs = options.retryBackoffMs ?? DEFAULTS.retryBackoffMs;
44
+ this.onDrop = options.onDrop;
45
+ this.onError = options.onError;
46
+ this.sessionId = v4_default();
47
+ const intervalMs = options.flushIntervalMs ?? DEFAULTS.flushIntervalMs;
48
+ this.flushTimer = setInterval(() => {
49
+ this.flush().catch((err) => {
50
+ this.handleError(err);
51
+ });
52
+ }, intervalMs);
53
+ if (this.flushTimer && typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
54
+ this.flushTimer.unref();
55
+ }
56
+ if (typeof globalThis !== "undefined" && typeof globalThis.addEventListener === "function") {
57
+ this.beforeUnloadHandler = () => {
58
+ this.flushSync();
59
+ };
60
+ globalThis.addEventListener("beforeunload", this.beforeUnloadHandler);
61
+ }
62
+ this.registerService();
63
+ }
64
+ // ── Logging Methods ──────────────────────────────────────────────
65
+ /** Log a debug-level message */
66
+ debug(symbol, message, data) {
67
+ this.log("debug", symbol, message, data);
68
+ }
69
+ /** Log an info-level message */
70
+ info(symbol, message, data) {
71
+ this.log("info", symbol, message, data);
72
+ }
73
+ /** Log a warn-level message */
74
+ warn(symbol, message, data) {
75
+ this.log("warn", symbol, message, data);
76
+ }
77
+ /** Log an error-level message */
78
+ error(symbol, message, data) {
79
+ this.log("error", symbol, message, data);
80
+ }
81
+ /** Log a message at the specified level */
82
+ log(level, symbol, message, data) {
83
+ if (this.closed) return;
84
+ const entry = {
85
+ level,
86
+ symbol,
87
+ message,
88
+ service: this.service,
89
+ sessionId: this.sessionId,
90
+ environment: this.environment,
91
+ data
92
+ };
93
+ this.pushToLogBuffer(entry);
94
+ }
95
+ // ── Metrics Methods ──────────────────────────────────────────────
96
+ /** Record a counter metric (increments) */
97
+ counter(name, value, tags) {
98
+ this.metric({ name, type: "counter", value: value ?? 1, tags });
99
+ }
100
+ /** Record a gauge metric (current value) */
101
+ gauge(name, value, tags) {
102
+ this.metric({ name, type: "gauge", value, tags });
103
+ }
104
+ /** Record a histogram metric (distribution) */
105
+ histogram(name, value, tags) {
106
+ this.metric({ name, type: "histogram", value, tags });
107
+ }
108
+ /** Record a metric of any type */
109
+ metric(input) {
110
+ if (this.closed) return;
111
+ const entry = {
112
+ ...input,
113
+ service: this.service,
114
+ environment: this.environment
115
+ };
116
+ this.pushToMetricsBuffer(entry);
117
+ }
118
+ // ── State Push ───────────────────────────────────────────────────
119
+ /** Push an application state snapshot to the server */
120
+ async pushState(state, activeFlows, activeGates) {
121
+ if (this.closed) return;
122
+ await this.post("/api/state", {
123
+ service: this.service,
124
+ sessionId: this.sessionId,
125
+ state,
126
+ activeFlows,
127
+ activeGates
128
+ });
129
+ }
130
+ // ── Tracing ──────────────────────────────────────────────────────
131
+ /** Start a trace span. Call end() on the returned SpanContext when the operation completes. */
132
+ startSpan(symbol, operation, parentSpanId) {
133
+ const traceId = parentSpanId ? parentSpanId.split("-")[0] || v4_default() : v4_default();
134
+ const spanId = v4_default();
135
+ const startTime = (/* @__PURE__ */ new Date()).toISOString();
136
+ const startMs = Date.now();
137
+ const self = this;
138
+ return {
139
+ traceId,
140
+ spanId,
141
+ async end(status = "ok") {
142
+ const endTime = (/* @__PURE__ */ new Date()).toISOString();
143
+ const durationMs = Date.now() - startMs;
144
+ const span = {
145
+ traceId,
146
+ spanId,
147
+ parentSpanId,
148
+ service: self.service,
149
+ symbol,
150
+ operation,
151
+ startTime,
152
+ endTime,
153
+ durationMs,
154
+ status
155
+ };
156
+ await self.post("/api/traces", span);
157
+ }
158
+ };
159
+ }
160
+ // ── Buffer Management ────────────────────────────────────────────
161
+ /** Flush all buffered logs and metrics to the server */
162
+ async flush() {
163
+ const logEntries = this.drainLogBuffer();
164
+ const metricEntries = this.drainMetricsBuffer();
165
+ const promises = [];
166
+ if (logEntries.length > 0) {
167
+ promises.push(this.sendLogs(logEntries));
168
+ }
169
+ if (metricEntries.length > 0) {
170
+ promises.push(this.sendMetrics(metricEntries));
171
+ }
172
+ await Promise.allSettled(promises);
173
+ }
174
+ // ── Lifecycle ────────────────────────────────────────────────────
175
+ /** Flush remaining entries and shut down the client */
176
+ async close() {
177
+ if (this.closed) return;
178
+ this.closed = true;
179
+ if (this.flushTimer !== null) {
180
+ clearInterval(this.flushTimer);
181
+ this.flushTimer = null;
182
+ }
183
+ if (this.beforeUnloadHandler && typeof globalThis !== "undefined" && typeof globalThis.removeEventListener === "function") {
184
+ globalThis.removeEventListener("beforeunload", this.beforeUnloadHandler);
185
+ this.beforeUnloadHandler = null;
186
+ }
187
+ try {
188
+ await this.flush();
189
+ } catch {
190
+ }
191
+ }
192
+ /** Get the session ID assigned to this client instance */
193
+ getSessionId() {
194
+ return this.sessionId;
195
+ }
196
+ /**
197
+ * Synchronous best-effort flush using sendBeacon (browser) or sync XHR fallback.
198
+ * Used in beforeunload where async is unreliable.
199
+ */
200
+ flushSync() {
201
+ const logEntries = this.drainLogBuffer();
202
+ const metricEntries = this.drainMetricsBuffer();
203
+ const sendBeacon = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" ? navigator.sendBeacon.bind(navigator) : void 0;
204
+ if (sendBeacon) {
205
+ if (logEntries.length > 0) {
206
+ sendBeacon(`${this.url}/api/logs`, JSON.stringify({ entries: logEntries }));
207
+ }
208
+ if (metricEntries.length > 0) {
209
+ sendBeacon(`${this.url}/api/metrics`, JSON.stringify({ entries: metricEntries }));
210
+ }
211
+ }
212
+ }
213
+ // ═══════════════════════════════════════════════════════════════════
214
+ // PRIVATE METHODS
215
+ // ═══════════════════════════════════════════════════════════════════
216
+ /** Register this service with the Sentinel server (fire-and-forget) */
217
+ registerService() {
218
+ const registration = {
219
+ name: this.service,
220
+ version: this.version,
221
+ pid: typeof process !== "undefined" ? process.pid : void 0,
222
+ environment: this.environment
223
+ };
224
+ this.post("/api/services", registration).catch(() => {
225
+ });
226
+ }
227
+ /** Push a log entry into the ring buffer, enforcing maxBufferSize */
228
+ pushToLogBuffer(entry) {
229
+ if (this.logBuffer.length >= this.maxBufferSize) {
230
+ const dropCount = Math.max(1, Math.floor(this.maxBufferSize * 0.1));
231
+ this.logBuffer.splice(0, dropCount);
232
+ if (this.onDrop) {
233
+ this.onDrop(dropCount);
234
+ }
235
+ }
236
+ this.logBuffer.push(entry);
237
+ if (this.logBuffer.length >= this.batchSize) {
238
+ this.flush().catch((err) => {
239
+ this.handleError(err);
240
+ });
241
+ }
242
+ }
243
+ /** Push a metric entry into the ring buffer, enforcing maxBufferSize */
244
+ pushToMetricsBuffer(entry) {
245
+ if (this.metricsBuffer.length >= this.maxBufferSize) {
246
+ const dropCount = Math.max(1, Math.floor(this.maxBufferSize * 0.1));
247
+ this.metricsBuffer.splice(0, dropCount);
248
+ if (this.onDrop) {
249
+ this.onDrop(dropCount);
250
+ }
251
+ }
252
+ this.metricsBuffer.push(entry);
253
+ if (this.metricsBuffer.length >= this.batchSize) {
254
+ this.flush().catch((err) => {
255
+ this.handleError(err);
256
+ });
257
+ }
258
+ }
259
+ /** Drain and return all entries from the log buffer */
260
+ drainLogBuffer() {
261
+ const entries = this.logBuffer;
262
+ this.logBuffer = [];
263
+ return entries;
264
+ }
265
+ /** Drain and return all entries from the metrics buffer */
266
+ drainMetricsBuffer() {
267
+ const entries = this.metricsBuffer;
268
+ this.metricsBuffer = [];
269
+ return entries;
270
+ }
271
+ /** Send log entries to the server with retry */
272
+ async sendLogs(entries) {
273
+ try {
274
+ await this.post("/api/logs", { entries });
275
+ } catch (err) {
276
+ const capacity = this.maxBufferSize - this.logBuffer.length;
277
+ if (capacity > 0) {
278
+ const toRestore = entries.slice(0, capacity);
279
+ this.logBuffer.unshift(...toRestore);
280
+ const dropped = entries.length - toRestore.length;
281
+ if (dropped > 0 && this.onDrop) {
282
+ this.onDrop(dropped);
283
+ }
284
+ } else if (this.onDrop) {
285
+ this.onDrop(entries.length);
286
+ }
287
+ throw err;
288
+ }
289
+ }
290
+ /** Send metric entries to the server with retry */
291
+ async sendMetrics(entries) {
292
+ try {
293
+ await this.post("/api/metrics", { entries });
294
+ } catch (err) {
295
+ const capacity = this.maxBufferSize - this.metricsBuffer.length;
296
+ if (capacity > 0) {
297
+ const toRestore = entries.slice(0, capacity);
298
+ this.metricsBuffer.unshift(...toRestore);
299
+ const dropped = entries.length - toRestore.length;
300
+ if (dropped > 0 && this.onDrop) {
301
+ this.onDrop(dropped);
302
+ }
303
+ } else if (this.onDrop) {
304
+ this.onDrop(entries.length);
305
+ }
306
+ throw err;
307
+ }
308
+ }
309
+ /**
310
+ * POST JSON to the Sentinel server with exponential backoff retry.
311
+ * Retries on network errors and 5xx responses. Does NOT retry on 4xx.
312
+ */
313
+ async post(path4, body) {
314
+ const fetchFn = this.getFetch();
315
+ if (!fetchFn) {
316
+ return void 0;
317
+ }
318
+ const url = `${this.url}${path4}`;
319
+ const headers = {
320
+ "Content-Type": "application/json"
321
+ };
322
+ if (this.token) {
323
+ headers["Authorization"] = `Bearer ${this.token}`;
324
+ }
325
+ let lastError;
326
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
327
+ try {
328
+ const response = await fetchFn(url, {
329
+ method: "POST",
330
+ headers,
331
+ body: JSON.stringify(body)
332
+ });
333
+ if (response.status >= 400 && response.status < 500) {
334
+ const text = await response.text().catch(() => "");
335
+ const err = new Error(`Sentinel server returned ${response.status}: ${text}`);
336
+ this.handleError(err);
337
+ return void 0;
338
+ }
339
+ if (response.status >= 500) {
340
+ const text = await response.text().catch(() => "");
341
+ lastError = new Error(`Sentinel server returned ${response.status}: ${text}`);
342
+ if (attempt < this.maxRetries) {
343
+ await this.backoff(attempt);
344
+ continue;
345
+ }
346
+ this.handleError(lastError);
347
+ throw lastError;
348
+ }
349
+ const contentType = response.headers.get("content-type") || "";
350
+ if (contentType.includes("application/json")) {
351
+ return await response.json();
352
+ }
353
+ return void 0;
354
+ } catch (err) {
355
+ lastError = err instanceof Error ? err : new Error(String(err));
356
+ if (attempt < this.maxRetries) {
357
+ await this.backoff(attempt);
358
+ continue;
359
+ }
360
+ this.handleError(lastError);
361
+ throw lastError;
362
+ }
363
+ }
364
+ if (lastError) throw lastError;
365
+ return void 0;
366
+ }
367
+ /** Get the fetch function, falling back to globalThis.fetch */
368
+ getFetch() {
369
+ if (typeof globalThis !== "undefined" && typeof globalThis.fetch === "function") {
370
+ return globalThis.fetch;
371
+ }
372
+ return void 0;
373
+ }
374
+ /** Wait with exponential backoff */
375
+ backoff(attempt) {
376
+ const delayMs = this.retryBackoffMs * Math.pow(2, attempt);
377
+ const jitter = Math.floor(Math.random() * delayMs * 0.25);
378
+ return new Promise((resolve) => setTimeout(resolve, delayMs + jitter));
379
+ }
380
+ /** Handle an error, calling the onError callback if provided */
381
+ handleError(err) {
382
+ if (this.onError) {
383
+ const error = err instanceof Error ? err : new Error(String(err));
384
+ this.onError(error);
385
+ }
386
+ }
387
+ };
388
+ function createSentinelClient(options) {
389
+ return new SentinelClient(options);
390
+ }
391
+ var SentinelTransport = class {
392
+ client;
393
+ constructor(client) {
394
+ this.client = client;
395
+ }
396
+ send(entry) {
397
+ this.client.log(
398
+ entry.level,
399
+ entry.symbol,
400
+ entry.message,
401
+ {
402
+ ...entry.data,
403
+ symbolType: entry.symbolType,
404
+ correlationId: entry.correlationId
405
+ }
406
+ );
407
+ }
408
+ };
409
+ function createSentinelTransport(clientOrOptions) {
410
+ const client = clientOrOptions instanceof SentinelClient ? clientOrOptions : new SentinelClient(clientOrOptions);
411
+ return new SentinelTransport(client);
412
+ }
413
+ function enableSentinel(logger, clientOrOptions) {
414
+ const transport = createSentinelTransport(clientOrOptions);
415
+ logger.addTransport(transport);
416
+ return transport;
417
+ }
5
418
 
6
419
  // ../sentinel/dist/index.js
7
420
  import * as path from "path";
@@ -12,8 +425,6 @@ import * as path2 from "path";
12
425
  import * as fs3 from "fs";
13
426
  import * as path3 from "path";
14
427
  import * as fs4 from "fs";
15
- import * as path4 from "path";
16
- import * as fs5 from "fs";
17
428
  var DEFAULT_CONFIG = {
18
429
  minScore: 30,
19
430
  maxResults: 5,
@@ -501,136 +912,6 @@ var Sentinel = class {
501
912
  return this.matcher;
502
913
  }
503
914
  };
504
- var CONFIG_FILES = [".sentinel.yaml", ".sentinel.yml"];
505
- function loadConfig(projectDir) {
506
- for (const filename of CONFIG_FILES) {
507
- const filePath = path2.join(projectDir, filename);
508
- if (fs2.existsSync(filePath)) {
509
- const content = fs2.readFileSync(filePath, "utf-8");
510
- return parseSimpleYaml(content);
511
- }
512
- }
513
- return null;
514
- }
515
- function writeConfig(projectDir, config) {
516
- const filePath = path2.join(projectDir, ".sentinel.yaml");
517
- const content = serializeSimpleYaml(config);
518
- fs2.writeFileSync(filePath, content, "utf-8");
519
- }
520
- function parseSimpleYaml(content) {
521
- const config = { version: "1.0", project: "" };
522
- const lines = content.split("\n");
523
- let currentSection = null;
524
- let currentSubSection = null;
525
- for (const line of lines) {
526
- const trimmed = line.trimEnd();
527
- if (!trimmed || trimmed.startsWith("#")) continue;
528
- const topMatch = trimmed.match(/^(\w+):\s*(.+)$/);
529
- if (topMatch) {
530
- const [, key, value] = topMatch;
531
- if (key === "version") config.version = value.replace(/['"]/g, "");
532
- else if (key === "project") config.project = value.replace(/['"]/g, "");
533
- else if (key === "environment") config.environment = value.replace(/['"]/g, "");
534
- currentSection = null;
535
- currentSubSection = null;
536
- continue;
537
- }
538
- const sectionMatch = trimmed.match(/^(\w+):$/);
539
- if (sectionMatch) {
540
- currentSection = sectionMatch[1];
541
- currentSubSection = null;
542
- if (currentSection === "symbols" && !config.symbols) {
543
- config.symbols = {};
544
- }
545
- if (currentSection === "routes" && !config.routes) {
546
- config.routes = {};
547
- }
548
- if (currentSection === "scrub" && !config.scrub) {
549
- config.scrub = {};
550
- }
551
- continue;
552
- }
553
- const subMatch = trimmed.match(/^\s{2}(\w+):$/);
554
- if (subMatch && currentSection) {
555
- currentSubSection = subMatch[1];
556
- if (currentSection === "symbols" && config.symbols) {
557
- config.symbols[currentSubSection] = [];
558
- }
559
- if (currentSection === "scrub" && config.scrub) {
560
- config.scrub[currentSubSection] = [];
561
- }
562
- continue;
563
- }
564
- const listMatch = trimmed.match(/^\s+-\s+(.+)$/);
565
- if (listMatch && currentSection && currentSubSection) {
566
- const value = listMatch[1].replace(/['"]/g, "");
567
- if (currentSection === "symbols" && config.symbols) {
568
- const arr = config.symbols[currentSubSection];
569
- if (Array.isArray(arr)) arr.push(value);
570
- }
571
- if (currentSection === "scrub" && config.scrub) {
572
- const arr = config.scrub[currentSubSection];
573
- if (Array.isArray(arr)) arr.push(value);
574
- }
575
- continue;
576
- }
577
- const routeMatch = trimmed.match(/^\s+(['"]?\/[^'"]+['"]?):\s+['"]?([^'"]+)['"]?$/);
578
- if (routeMatch && currentSection === "routes" && config.routes) {
579
- const route = routeMatch[1].replace(/['"]/g, "");
580
- config.routes[route] = routeMatch[2];
581
- continue;
582
- }
583
- }
584
- return config;
585
- }
586
- function serializeSimpleYaml(config) {
587
- const lines = [];
588
- lines.push(`# Sentinel Configuration`);
589
- lines.push(`# Auto-generated \u2014 edit freely`);
590
- lines.push("");
591
- lines.push(`version: "${config.version}"`);
592
- lines.push(`project: "${config.project}"`);
593
- if (config.environment) {
594
- lines.push(`environment: "${config.environment}"`);
595
- }
596
- if (config.symbols) {
597
- lines.push("");
598
- lines.push("symbols:");
599
- for (const [key, values] of Object.entries(config.symbols)) {
600
- if (values && values.length > 0) {
601
- lines.push(` ${key}:`);
602
- for (const v of values) {
603
- lines.push(` - ${v}`);
604
- }
605
- }
606
- }
607
- }
608
- if (config.routes && Object.keys(config.routes).length > 0) {
609
- lines.push("");
610
- lines.push("routes:");
611
- for (const [route, symbol] of Object.entries(config.routes)) {
612
- lines.push(` "${route}": ${symbol}`);
613
- }
614
- }
615
- if (config.scrub) {
616
- lines.push("");
617
- lines.push("scrub:");
618
- if (config.scrub.headers?.length) {
619
- lines.push(" headers:");
620
- for (const h of config.scrub.headers) {
621
- lines.push(` - ${h}`);
622
- }
623
- }
624
- if (config.scrub.fields?.length) {
625
- lines.push(" fields:");
626
- for (const f of config.scrub.fields) {
627
- lines.push(` - ${f}`);
628
- }
629
- }
630
- }
631
- lines.push("");
632
- return lines.join("\n");
633
- }
634
915
  var DIR_PATTERNS = [
635
916
  { dirs: ["services", "src/services"], prefix: "#", type: "components" },
636
917
  { dirs: ["routes", "src/routes", "api", "src/api"], prefix: "#", type: "components" },
@@ -665,13 +946,13 @@ function detectSymbols(projectDir) {
665
946
  }
666
947
  for (const pattern of DIR_PATTERNS) {
667
948
  for (const dir of pattern.dirs) {
668
- const fullPath = path3.join(projectDir, dir);
669
- if (!fs3.existsSync(fullPath)) continue;
949
+ const fullPath = path2.join(projectDir, dir);
950
+ if (!fs2.existsSync(fullPath)) continue;
670
951
  const files = safeReaddir(fullPath);
671
952
  for (const file of files) {
672
- const ext = path3.extname(file);
953
+ const ext = path2.extname(file);
673
954
  if (!CODE_EXTENSIONS.has(ext)) continue;
674
- const name = path3.basename(file, ext);
955
+ const name = path2.basename(file, ext);
675
956
  if (name === "index" || name.endsWith(".test") || name.endsWith(".spec")) continue;
676
957
  const symbol = `${pattern.prefix}${toKebabCase(name)}`;
677
958
  if (!result[pattern.type].includes(symbol)) {
@@ -687,7 +968,7 @@ function generateConfig(projectDir) {
687
968
  const detected = detectSymbols(projectDir);
688
969
  return {
689
970
  version: "1.0",
690
- project: path3.basename(projectDir),
971
+ project: path2.basename(projectDir),
691
972
  symbols: {
692
973
  components: detected.components.length > 0 ? detected.components : void 0,
693
974
  gates: detected.gates.length > 0 ? detected.gates : void 0,
@@ -698,8 +979,8 @@ function generateConfig(projectDir) {
698
979
  };
699
980
  }
700
981
  function readPurposeFiles(projectDir) {
701
- const paradigmDir = path3.join(projectDir, ".paradigm");
702
- if (!fs3.existsSync(paradigmDir)) return null;
982
+ const paradigmDir = path2.join(projectDir, ".paradigm");
983
+ if (!fs2.existsSync(paradigmDir)) return null;
703
984
  const result = {
704
985
  components: [],
705
986
  gates: [],
@@ -710,7 +991,7 @@ function readPurposeFiles(projectDir) {
710
991
  const purposeFiles = findFiles(projectDir, ".purpose");
711
992
  for (const file of purposeFiles) {
712
993
  try {
713
- const content = fs3.readFileSync(file, "utf-8");
994
+ const content = fs2.readFileSync(file, "utf-8");
714
995
  extractPurposeSymbols(content, result);
715
996
  } catch {
716
997
  }
@@ -763,13 +1044,13 @@ function extractPurposeSymbols(content, result) {
763
1044
  function scanRoutes(projectDir, result) {
764
1045
  const routeDirs = ["routes", "src/routes", "api", "src/api"];
765
1046
  for (const dir of routeDirs) {
766
- const fullPath = path3.join(projectDir, dir);
767
- if (!fs3.existsSync(fullPath)) continue;
1047
+ const fullPath = path2.join(projectDir, dir);
1048
+ if (!fs2.existsSync(fullPath)) continue;
768
1049
  const files = safeReaddir(fullPath);
769
1050
  for (const file of files) {
770
- const ext = path3.extname(file);
1051
+ const ext = path2.extname(file);
771
1052
  if (!CODE_EXTENSIONS.has(ext)) continue;
772
- const name = path3.basename(file, ext);
1053
+ const name = path2.basename(file, ext);
773
1054
  if (name === "index") continue;
774
1055
  const routePrefix = `/api/${toKebabCase(name)}`;
775
1056
  const component = `#${toKebabCase(name)}`;
@@ -782,10 +1063,10 @@ function toKebabCase(str) {
782
1063
  }
783
1064
  function safeReaddir(dir) {
784
1065
  try {
785
- return fs3.readdirSync(dir).filter((f) => {
786
- const fullPath = path3.join(dir, f);
1066
+ return fs2.readdirSync(dir).filter((f) => {
1067
+ const fullPath = path2.join(dir, f);
787
1068
  try {
788
- return fs3.statSync(fullPath).isFile();
1069
+ return fs2.statSync(fullPath).isFile();
789
1070
  } catch {
790
1071
  return false;
791
1072
  }
@@ -799,23 +1080,30 @@ function findFiles(dir, filename, maxDepth = 4, depth = 0) {
799
1080
  const results = [];
800
1081
  const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".next", ".nuxt"]);
801
1082
  try {
802
- const entries = fs3.readdirSync(dir, { withFileTypes: true });
1083
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
803
1084
  for (const entry of entries) {
804
1085
  if (entry.isFile() && entry.name === filename) {
805
- results.push(path3.join(dir, entry.name));
1086
+ results.push(path2.join(dir, entry.name));
806
1087
  } else if (entry.isDirectory() && !skipDirs.has(entry.name)) {
807
- results.push(...findFiles(path3.join(dir, entry.name), filename, maxDepth, depth + 1));
1088
+ results.push(...findFiles(path2.join(dir, entry.name), filename, maxDepth, depth + 1));
808
1089
  }
809
1090
  }
810
1091
  } catch {
811
1092
  }
812
1093
  return results;
813
1094
  }
814
- var SIMILARITY_THRESHOLD = 0.6;
1095
+ var DEFAULT_SIMILARITY_THRESHOLD = 0.6;
1096
+ var DECAY_HALF_LIFE_DAYS = 14;
815
1097
  var IncidentGrouper = class {
816
- constructor(storage) {
1098
+ constructor(storage, config) {
817
1099
  this.storage = storage;
1100
+ this.similarityThreshold = config?.similarityThreshold ?? DEFAULT_SIMILARITY_THRESHOLD;
1101
+ this.decayHalfLifeDays = config?.decayHalfLifeDays ?? DECAY_HALF_LIFE_DAYS;
1102
+ this.useStackFingerprint = config?.useStackFingerprint ?? true;
818
1103
  }
1104
+ similarityThreshold;
1105
+ decayHalfLifeDays;
1106
+ useStackFingerprint;
819
1107
  /**
820
1108
  * Try to find or create a group for an incident
821
1109
  * Returns the group ID if grouped, null if no suitable group
@@ -861,7 +1149,7 @@ var IncidentGrouper = class {
861
1149
  continue;
862
1150
  }
863
1151
  const score = this.calculateSimilarity(incident, candidate);
864
- if (score >= SIMILARITY_THRESHOLD) {
1152
+ if (score >= this.similarityThreshold) {
865
1153
  similar.push({ incident: candidate, score });
866
1154
  }
867
1155
  }
@@ -882,7 +1170,7 @@ var IncidentGrouper = class {
882
1170
  continue;
883
1171
  }
884
1172
  const similar = ungrouped.filter(
885
- (other) => other.id !== incident.id && !processed.has(other.id) && this.calculateSimilarity(incident, other) >= SIMILARITY_THRESHOLD
1173
+ (other) => other.id !== incident.id && !processed.has(other.id) && this.calculateSimilarity(incident, other) >= this.similarityThreshold
886
1174
  );
887
1175
  if (similar.length + 1 >= minSize) {
888
1176
  const members = [incident, ...similar];
@@ -909,11 +1197,17 @@ var IncidentGrouper = class {
909
1197
  }
910
1198
  /**
911
1199
  * Calculate similarity between two incidents (0-1)
1200
+ * Applies time-decay so older incidents contribute less, and optionally
1201
+ * uses stack trace fingerprinting for more accurate grouping.
912
1202
  */
913
1203
  calculateSimilarity(a, b) {
914
1204
  let score = 0;
915
1205
  let maxScore = 0;
916
- const symbolWeight = 0.6;
1206
+ const hasStacks = this.useStackFingerprint && a.error.stack && b.error.stack;
1207
+ const symbolWeight = hasStacks ? 0.45 : 0.6;
1208
+ const errorWeight = hasStacks ? 0.25 : 0.3;
1209
+ const envWeight = 0.1;
1210
+ const stackWeight = hasStacks ? 0.2 : 0;
917
1211
  const symbolTypes = [
918
1212
  "feature",
919
1213
  "component",
@@ -933,19 +1227,55 @@ var IncidentGrouper = class {
933
1227
  }
934
1228
  }
935
1229
  }
936
- const errorWeight = 0.3;
937
1230
  const errorSimilarity = this.stringSimilarity(
938
1231
  a.error.message,
939
1232
  b.error.message
940
1233
  );
941
1234
  score += errorWeight * errorSimilarity;
942
1235
  maxScore += errorWeight;
943
- const envWeight = 0.1;
944
1236
  if (a.environment === b.environment) {
945
1237
  score += envWeight;
946
1238
  }
947
1239
  maxScore += envWeight;
948
- return maxScore > 0 ? score / maxScore : 0;
1240
+ if (hasStacks) {
1241
+ const aFingerprint = this.fingerprintStack(a.error.stack);
1242
+ const bFingerprint = this.fingerprintStack(b.error.stack);
1243
+ const stackSimilarity = this.compareFingerprints(aFingerprint, bFingerprint);
1244
+ score += stackWeight * stackSimilarity;
1245
+ maxScore += stackWeight;
1246
+ }
1247
+ const rawScore = maxScore > 0 ? score / maxScore : 0;
1248
+ const timeDelta = Math.abs(
1249
+ new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
1250
+ );
1251
+ const daysDelta = timeDelta / (1e3 * 60 * 60 * 24);
1252
+ const decayFactor = Math.pow(0.5, daysDelta / this.decayHalfLifeDays);
1253
+ return rawScore * decayFactor;
1254
+ }
1255
+ /**
1256
+ * Extract a fingerprint from a stack trace by normalizing frames.
1257
+ * Strips line numbers, column numbers, and absolute paths to capture
1258
+ * the structural signature of the call stack.
1259
+ */
1260
+ fingerprintStack(stack) {
1261
+ return stack.split("\n").filter((line) => line.trim().startsWith("at ")).slice(0, 10).map((frame) => {
1262
+ return frame.trim().replace(/:\d+:\d+\)?$/, "").replace(/\(.*[/\\]/, "(").replace(/^\s*at\s+/, "");
1263
+ });
1264
+ }
1265
+ /**
1266
+ * Compare two stack fingerprints (0-1 similarity)
1267
+ */
1268
+ compareFingerprints(a, b) {
1269
+ if (a.length === 0 && b.length === 0) return 1;
1270
+ if (a.length === 0 || b.length === 0) return 0;
1271
+ let matches = 0;
1272
+ const maxLen = Math.max(a.length, b.length);
1273
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
1274
+ if (a[i] === b[i]) {
1275
+ matches++;
1276
+ }
1277
+ }
1278
+ return matches / maxLen;
949
1279
  }
950
1280
  /**
951
1281
  * Calculate string similarity using Levenshtein distance
@@ -1550,12 +1880,12 @@ var ContextEnricher = class {
1550
1880
  * Find symbol in premise index
1551
1881
  */
1552
1882
  findInSymbolIndex(symbol) {
1553
- const indexPath = path4.join(this.projectRoot, ".paradigm", "index.json");
1554
- if (!fs4.existsSync(indexPath)) {
1883
+ const indexPath = path3.join(this.projectRoot, ".paradigm", "index.json");
1884
+ if (!fs3.existsSync(indexPath)) {
1555
1885
  return null;
1556
1886
  }
1557
1887
  try {
1558
- const indexContent = fs4.readFileSync(indexPath, "utf-8");
1888
+ const indexContent = fs3.readFileSync(indexPath, "utf-8");
1559
1889
  const index = JSON.parse(indexContent);
1560
1890
  if (index.symbols && Array.isArray(index.symbols)) {
1561
1891
  return index.symbols.find(
@@ -1573,8 +1903,8 @@ var ContextEnricher = class {
1573
1903
  findInPurposeFiles(symbol) {
1574
1904
  const searchPaths = this.getSearchPathsForSymbol(symbol);
1575
1905
  for (const searchPath of searchPaths) {
1576
- const fullPath = path4.join(this.projectRoot, searchPath);
1577
- if (!fs4.existsSync(fullPath)) {
1906
+ const fullPath = path3.join(this.projectRoot, searchPath);
1907
+ if (!fs3.existsSync(fullPath)) {
1578
1908
  continue;
1579
1909
  }
1580
1910
  const cached = this.purposeCache.get(fullPath);
@@ -1585,7 +1915,7 @@ var ContextEnricher = class {
1585
1915
  continue;
1586
1916
  }
1587
1917
  try {
1588
- const content = fs4.readFileSync(fullPath, "utf-8");
1918
+ const content = fs3.readFileSync(fullPath, "utf-8");
1589
1919
  const purpose = this.parsePurposeFile(content);
1590
1920
  this.purposeCache.set(fullPath, purpose);
1591
1921
  if (purpose.symbol === symbol) {
@@ -1615,11 +1945,11 @@ var ContextEnricher = class {
1615
1945
  const prefix = symbol[0];
1616
1946
  const dirs = prefixDirs[prefix] || [];
1617
1947
  for (const dir of dirs) {
1618
- paths.push(path4.join(dir, cleanSymbol, ".purpose"));
1619
- paths.push(path4.join(dir, `${cleanSymbol}.purpose`));
1948
+ paths.push(path3.join(dir, cleanSymbol, ".purpose"));
1949
+ paths.push(path3.join(dir, `${cleanSymbol}.purpose`));
1620
1950
  }
1621
- paths.push(path4.join(".paradigm", "purposes", `${cleanSymbol}.yaml`));
1622
- paths.push(path4.join(".paradigm", "purposes", `${cleanSymbol}.json`));
1951
+ paths.push(path3.join(".paradigm", "purposes", `${cleanSymbol}.yaml`));
1952
+ paths.push(path3.join(".paradigm", "purposes", `${cleanSymbol}.json`));
1623
1953
  return paths;
1624
1954
  }
1625
1955
  /**
@@ -1682,7 +2012,7 @@ var PatternSuggester = class {
1682
2012
  },
1683
2013
  resolution: {
1684
2014
  description: incident.resolution?.notes || "Resolution approach TBD",
1685
- strategy: "fix-code",
2015
+ strategy: this.inferStrategy([incident]),
1686
2016
  priority: "medium"
1687
2017
  },
1688
2018
  source: "suggested",
@@ -1697,6 +2027,7 @@ var PatternSuggester = class {
1697
2027
  suggestFromGroup(group) {
1698
2028
  const baseId = `group-${group.id.toLowerCase().replace(/[^a-z0-9]/g, "-")}`;
1699
2029
  const symbols = this.buildSymbolCriteria(group.commonSymbols);
2030
+ const groupIncidents = group.incidents.slice(0, 20).map((id) => this.storage.getIncident(id)).filter((i) => i != null);
1700
2031
  const pattern = {
1701
2032
  id: baseId,
1702
2033
  name: group.name || `Pattern from group ${group.id}`,
@@ -1707,7 +2038,7 @@ var PatternSuggester = class {
1707
2038
  },
1708
2039
  resolution: {
1709
2040
  description: "Resolution approach TBD based on grouped incidents",
1710
- strategy: "fix-code",
2041
+ strategy: groupIncidents.length > 0 ? this.inferStrategy(groupIncidents) : "fix-code",
1711
2042
  priority: this.getPriorityFromCount(group.count)
1712
2043
  },
1713
2044
  source: "suggested",
@@ -2006,21 +2337,38 @@ var PatternSuggester = class {
2006
2337
  return false;
2007
2338
  }
2008
2339
  /**
2009
- * Infer resolution strategy from incidents
2340
+ * Infer resolution strategy from incident error patterns and context.
2341
+ * Uses keyword heuristics across all incident messages to pick the
2342
+ * most likely resolution approach.
2010
2343
  */
2011
2344
  inferStrategy(incidents) {
2012
2345
  const messages = incidents.map((i) => i.error.message.toLowerCase());
2013
- if (messages.some((m) => m.includes("timeout") || m.includes("network"))) {
2346
+ const hasKeyword = (keywords) => messages.some((m) => keywords.some((k) => m.includes(k)));
2347
+ if (hasKeyword(["revert", "rollback", "regression", "broke after deploy", "since deploy"])) {
2348
+ return "rollback";
2349
+ }
2350
+ if (hasKeyword(["config", "environment variable", "env var", "missing key", "secret", "credential"])) {
2351
+ return "config-change";
2352
+ }
2353
+ if (hasKeyword(["out of memory", "oom", "heap", "memory limit", "capacity", "too many connections", "pool exhausted"])) {
2354
+ return "scale-up";
2355
+ }
2356
+ if (hasKeyword(["timeout", "network", "econnrefused", "econnreset", "dns", "socket hang up"])) {
2014
2357
  return "retry";
2015
2358
  }
2016
- if (messages.some(
2017
- (m) => m.includes("validation") || m.includes("invalid") || m.includes("required")
2018
- )) {
2359
+ if (hasKeyword(["unavailable", "service down", "circuit breaker", "fallback", "503", "502"])) {
2360
+ return "fallback";
2361
+ }
2362
+ if (hasKeyword(["validation", "invalid", "required", "constraint", "duplicate", "not found", "404"])) {
2019
2363
  return "fix-data";
2020
2364
  }
2021
- if (messages.some((m) => m.includes("permission") || m.includes("403"))) {
2365
+ if (hasKeyword(["permission", "forbidden", "403", "401", "unauthorized", "access denied"])) {
2022
2366
  return "escalate";
2023
2367
  }
2368
+ const uniqueTypes = new Set(incidents.map((i) => i.error.type).filter(Boolean));
2369
+ if (uniqueTypes.size > 2) {
2370
+ return "investigate";
2371
+ }
2024
2372
  return "fix-code";
2025
2373
  }
2026
2374
  /**
@@ -2127,10 +2475,10 @@ var PatternImporter = class {
2127
2475
  * Load patterns from a JSON file
2128
2476
  */
2129
2477
  loadFromFile(filePath) {
2130
- if (!fs5.existsSync(filePath)) {
2478
+ if (!fs4.existsSync(filePath)) {
2131
2479
  throw new Error(`File not found: ${filePath}`);
2132
2480
  }
2133
- const content = fs5.readFileSync(filePath, "utf-8");
2481
+ const content = fs4.readFileSync(filePath, "utf-8");
2134
2482
  const data = JSON.parse(content);
2135
2483
  const validation = this.validate(data);
2136
2484
  if (!validation.valid) {
@@ -2232,14 +2580,17 @@ var PatternImporter = class {
2232
2580
  };
2233
2581
 
2234
2582
  export {
2583
+ SentinelClient,
2584
+ createSentinelClient,
2585
+ SentinelTransport,
2586
+ createSentinelTransport,
2587
+ enableSentinel,
2235
2588
  PatternMatcher,
2236
2589
  loadUniversalPatterns,
2237
2590
  loadParadigmPatterns,
2238
2591
  loadAllSeedPatterns,
2239
2592
  FlowTracker,
2240
2593
  Sentinel,
2241
- loadConfig,
2242
- writeConfig,
2243
2594
  detectSymbols,
2244
2595
  generateConfig,
2245
2596
  IncidentGrouper,