@a-company/paradigm 3.0.2 → 3.1.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.
- package/dist/{triage-RM5KNG5V.js → chunk-4LGLU2LO.js} +1035 -663
- package/dist/{chunk-4WR7X3FE.js → chunk-LRSJNX7K.js} +26 -0
- package/dist/{chunk-S65LENNL.js → chunk-VZ7CXFRZ.js} +248 -3
- package/dist/{chunk-27OSFWHG.js → chunk-X3ROB27T.js} +54 -0
- package/dist/dist-GPQ4LAY3.js +42 -0
- package/dist/habits-YVCOZ2LC.js +485 -0
- package/dist/{hooks-7TQIRXXS.js → hooks-ZVGXLK6Z.js} +1 -1
- package/dist/index.js +49 -28
- package/dist/{list-QMUE7DPK.js → list-SDYF6T7M.js} +1 -1
- package/dist/mcp.js +1177 -99
- package/dist/{record-5CTCDFUO.js → record-5IY5RWTI.js} +1 -1
- package/dist/{review-QEDNQAIO.js → review-CSA223ZJ.js} +1 -1
- package/dist/{sentinel-RSEXIRXM.js → sentinel-WB7GIK4V.js} +1 -1
- package/dist/{server-NXG5N7JE.js → server-MV4HNFVF.js} +1 -1
- package/dist/{shift-NABNKPGL.js → shift-DLV5YLFJ.js} +1 -1
- package/dist/{show-S653P3TO.js → show-QRV7CWKT.js} +1 -1
- package/dist/triage-TBIWJA6R.js +671 -0
- package/dist/university-content/courses/para-501.json +486 -0
- package/dist/university-content/plsat/v3.0.json +233 -0
- package/dist/university-content/reference.json +61 -0
- package/package.json +1 -1
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
SentinelStorage
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-MO4EEYFW.js";
|
|
6
|
-
|
|
7
|
-
// src/commands/triage/index.ts
|
|
8
|
-
import chalk2 from "chalk";
|
|
9
|
-
import ora from "ora";
|
|
4
|
+
} from "./chunk-VZ7CXFRZ.js";
|
|
10
5
|
|
|
11
6
|
// ../sentinel/dist/index.js
|
|
12
7
|
import * as path from "path";
|
|
13
8
|
import * as fs from "fs";
|
|
14
9
|
import { fileURLToPath } from "url";
|
|
10
|
+
import * as fs2 from "fs";
|
|
11
|
+
import * as path2 from "path";
|
|
12
|
+
import * as fs3 from "fs";
|
|
13
|
+
import * as path3 from "path";
|
|
14
|
+
import * as fs4 from "fs";
|
|
15
|
+
import * as path4 from "path";
|
|
15
16
|
import * as fs5 from "fs";
|
|
16
17
|
var DEFAULT_CONFIG = {
|
|
17
18
|
minScore: 30,
|
|
@@ -19,8 +20,8 @@ var DEFAULT_CONFIG = {
|
|
|
19
20
|
boostConfidence: true
|
|
20
21
|
};
|
|
21
22
|
var PatternMatcher = class {
|
|
22
|
-
constructor(
|
|
23
|
-
this.storage =
|
|
23
|
+
constructor(storage) {
|
|
24
|
+
this.storage = storage;
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* Match an incident against all patterns and return ranked results
|
|
@@ -253,6 +254,831 @@ function loadAllSeedPatterns() {
|
|
|
253
254
|
patterns: [...universal.patterns, ...paradigm.patterns]
|
|
254
255
|
};
|
|
255
256
|
}
|
|
257
|
+
function ensurePrefix(id, prefix) {
|
|
258
|
+
return id.startsWith(prefix) ? id : `${prefix}${id}`;
|
|
259
|
+
}
|
|
260
|
+
var FlowTracker = class {
|
|
261
|
+
flowId;
|
|
262
|
+
sentinel;
|
|
263
|
+
actual = [];
|
|
264
|
+
expected = [];
|
|
265
|
+
completed = false;
|
|
266
|
+
constructor(flowId, sentinel) {
|
|
267
|
+
this.flowId = ensurePrefix(flowId, "$");
|
|
268
|
+
this.sentinel = sentinel;
|
|
269
|
+
}
|
|
270
|
+
/** Declare which signals/gates are expected in this flow */
|
|
271
|
+
expect(...symbols) {
|
|
272
|
+
this.expected.push(...symbols);
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
/** Record a generic step in the flow */
|
|
276
|
+
step(symbol) {
|
|
277
|
+
this.actual.push(symbol);
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
/** Record a gate check result */
|
|
281
|
+
gate(id, passed) {
|
|
282
|
+
const gateId = ensurePrefix(id, "^");
|
|
283
|
+
this.actual.push(gateId);
|
|
284
|
+
if (!passed) {
|
|
285
|
+
this.fail(new Error(`Gate ${gateId} failed`));
|
|
286
|
+
}
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
/** Record a signal emission */
|
|
290
|
+
signal(id, _data) {
|
|
291
|
+
this.actual.push(ensurePrefix(id, "!"));
|
|
292
|
+
return this;
|
|
293
|
+
}
|
|
294
|
+
/** Mark the flow as successfully completed */
|
|
295
|
+
complete() {
|
|
296
|
+
this.completed = true;
|
|
297
|
+
}
|
|
298
|
+
/** Capture an error with full flow position context */
|
|
299
|
+
fail(error) {
|
|
300
|
+
if (this.completed) return;
|
|
301
|
+
this.completed = true;
|
|
302
|
+
const missing = this.expected.filter((s) => !this.actual.includes(s));
|
|
303
|
+
const failedAt = this.actual.length > 0 ? this.actual[this.actual.length - 1] : void 0;
|
|
304
|
+
const flowPosition = {
|
|
305
|
+
flowId: this.flowId,
|
|
306
|
+
expected: this.expected,
|
|
307
|
+
actual: this.actual,
|
|
308
|
+
missing,
|
|
309
|
+
failedAt
|
|
310
|
+
};
|
|
311
|
+
this.sentinel.capture(error, { flow: this.flowId }, flowPosition);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
var Sentinel = class {
|
|
315
|
+
storage;
|
|
316
|
+
matcher;
|
|
317
|
+
config;
|
|
318
|
+
ready = false;
|
|
319
|
+
readyPromise = null;
|
|
320
|
+
seeded = false;
|
|
321
|
+
constructor(config) {
|
|
322
|
+
this.config = config;
|
|
323
|
+
this.storage = new SentinelStorage(config.dbPath);
|
|
324
|
+
this.matcher = new PatternMatcher(this.storage);
|
|
325
|
+
}
|
|
326
|
+
/** Explicitly initialize storage. Optional — auto-called on first capture. */
|
|
327
|
+
async init() {
|
|
328
|
+
if (this.ready) return;
|
|
329
|
+
if (this.readyPromise) return this.readyPromise;
|
|
330
|
+
this.readyPromise = this.doInit();
|
|
331
|
+
return this.readyPromise;
|
|
332
|
+
}
|
|
333
|
+
async doInit() {
|
|
334
|
+
await this.storage.ensureReady();
|
|
335
|
+
if (!this.seeded) {
|
|
336
|
+
try {
|
|
337
|
+
const { patterns } = loadAllSeedPatterns();
|
|
338
|
+
for (const pattern of patterns) {
|
|
339
|
+
try {
|
|
340
|
+
this.storage.addPattern(pattern);
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
this.seeded = true;
|
|
347
|
+
}
|
|
348
|
+
this.ready = true;
|
|
349
|
+
}
|
|
350
|
+
ensureReady() {
|
|
351
|
+
if (!this.ready) {
|
|
352
|
+
if (!this.readyPromise) {
|
|
353
|
+
this.readyPromise = this.doInit();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// ── Symbol Context ──────────────────────────────────────────────
|
|
358
|
+
/**
|
|
359
|
+
* Create a component context for scoped error capture.
|
|
360
|
+
*
|
|
361
|
+
* @param id - Component symbol (e.g. '#checkout' or 'checkout')
|
|
362
|
+
* @returns ComponentContext with capture() and wrap() methods
|
|
363
|
+
*/
|
|
364
|
+
component(id) {
|
|
365
|
+
const componentId = ensurePrefix(id, "#");
|
|
366
|
+
const self = this;
|
|
367
|
+
return {
|
|
368
|
+
id: componentId,
|
|
369
|
+
capture(error, extra) {
|
|
370
|
+
return self.capture(error, { component: componentId, ...extra });
|
|
371
|
+
},
|
|
372
|
+
wrap(fn) {
|
|
373
|
+
const wrapped = ((...args) => {
|
|
374
|
+
try {
|
|
375
|
+
const result = fn(...args);
|
|
376
|
+
if (result && typeof result.catch === "function") {
|
|
377
|
+
return result.catch((err) => {
|
|
378
|
+
self.capture(err, { component: componentId });
|
|
379
|
+
throw err;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return result;
|
|
383
|
+
} catch (err) {
|
|
384
|
+
if (err instanceof Error) {
|
|
385
|
+
self.capture(err, { component: componentId });
|
|
386
|
+
}
|
|
387
|
+
throw err;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
return wrapped;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Record a gate check result.
|
|
396
|
+
* If the gate fails, auto-captures an incident.
|
|
397
|
+
*
|
|
398
|
+
* @param id - Gate symbol (e.g. '^authenticated' or 'authenticated')
|
|
399
|
+
* @param passed - Whether the gate passed
|
|
400
|
+
*/
|
|
401
|
+
gate(id, passed) {
|
|
402
|
+
if (!passed) {
|
|
403
|
+
const gateId = ensurePrefix(id, "^");
|
|
404
|
+
this.capture(new Error(`Gate ${gateId} failed`), { gate: gateId });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Record a signal emission. Primarily for flow tracking context.
|
|
409
|
+
*
|
|
410
|
+
* @param id - Signal symbol (e.g. '!payment-authorized' or 'payment-authorized')
|
|
411
|
+
*/
|
|
412
|
+
signal(id, _data) {
|
|
413
|
+
void ensurePrefix(id, "!");
|
|
414
|
+
}
|
|
415
|
+
// ── Flow Tracking ───────────────────────────────────────────────
|
|
416
|
+
/**
|
|
417
|
+
* Create a flow tracker for monitoring multi-step operations.
|
|
418
|
+
*
|
|
419
|
+
* @param id - Flow symbol (e.g. '$checkout-flow' or 'checkout-flow')
|
|
420
|
+
* @returns FlowTracker instance
|
|
421
|
+
*/
|
|
422
|
+
flow(id) {
|
|
423
|
+
return new FlowTracker(id, this);
|
|
424
|
+
}
|
|
425
|
+
// ── Error Capture ───────────────────────────────────────────────
|
|
426
|
+
/**
|
|
427
|
+
* Capture an error with symbolic context.
|
|
428
|
+
*
|
|
429
|
+
* @param error - The error to capture
|
|
430
|
+
* @param context - Symbolic context (component, gate, flow, signal)
|
|
431
|
+
* @param flowPosition - Optional flow position data
|
|
432
|
+
* @returns Incident ID (e.g. 'INC-001')
|
|
433
|
+
*/
|
|
434
|
+
capture(error, context, flowPosition) {
|
|
435
|
+
this.ensureReady();
|
|
436
|
+
const input = {
|
|
437
|
+
error: {
|
|
438
|
+
message: error.message,
|
|
439
|
+
stack: error.stack,
|
|
440
|
+
type: error.constructor.name !== "Error" ? error.constructor.name : void 0
|
|
441
|
+
},
|
|
442
|
+
symbols: context || {},
|
|
443
|
+
environment: this.config.environment || "development",
|
|
444
|
+
service: this.config.service,
|
|
445
|
+
version: this.config.version,
|
|
446
|
+
flowPosition
|
|
447
|
+
};
|
|
448
|
+
const incidentId = this.storage.recordIncident(input);
|
|
449
|
+
const incident = this.storage.getIncident(incidentId);
|
|
450
|
+
if (incident && this.config.onCapture) {
|
|
451
|
+
this.config.onCapture(incident);
|
|
452
|
+
}
|
|
453
|
+
return incidentId;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Get pattern matches for a captured incident.
|
|
457
|
+
*
|
|
458
|
+
* @param incidentId - The incident ID to match
|
|
459
|
+
* @returns Array of pattern matches sorted by confidence
|
|
460
|
+
*/
|
|
461
|
+
match(incidentId) {
|
|
462
|
+
const incident = this.storage.getIncident(incidentId);
|
|
463
|
+
if (!incident) return [];
|
|
464
|
+
return this.matcher.match(incident);
|
|
465
|
+
}
|
|
466
|
+
// ── Framework Integration ───────────────────────────────────────
|
|
467
|
+
/**
|
|
468
|
+
* Create Express error-handling middleware.
|
|
469
|
+
*
|
|
470
|
+
* Usage:
|
|
471
|
+
* app.use(sentinel.express());
|
|
472
|
+
*/
|
|
473
|
+
express() {
|
|
474
|
+
const self = this;
|
|
475
|
+
return (err, req, res, next) => {
|
|
476
|
+
const context = {};
|
|
477
|
+
const routeParts = (req.path || req.url || "").split("/").filter(Boolean);
|
|
478
|
+
if (routeParts.length >= 2) {
|
|
479
|
+
context.component = `#${routeParts[1]}`;
|
|
480
|
+
}
|
|
481
|
+
const incidentId = self.capture(err, context);
|
|
482
|
+
if (res.setHeader) {
|
|
483
|
+
res.setHeader("X-Sentinel-Incident", incidentId);
|
|
484
|
+
}
|
|
485
|
+
next(err);
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
// ── Lifecycle ───────────────────────────────────────────────────
|
|
489
|
+
/** Close the database connection. Call when shutting down. */
|
|
490
|
+
close() {
|
|
491
|
+
this.storage.close();
|
|
492
|
+
this.ready = false;
|
|
493
|
+
this.readyPromise = null;
|
|
494
|
+
}
|
|
495
|
+
/** Get the underlying storage instance (for advanced usage). */
|
|
496
|
+
getStorage() {
|
|
497
|
+
return this.storage;
|
|
498
|
+
}
|
|
499
|
+
/** Get the underlying pattern matcher (for advanced usage). */
|
|
500
|
+
getMatcher() {
|
|
501
|
+
return this.matcher;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
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
|
+
var DIR_PATTERNS = [
|
|
635
|
+
{ dirs: ["services", "src/services"], prefix: "#", type: "components" },
|
|
636
|
+
{ dirs: ["routes", "src/routes", "api", "src/api"], prefix: "#", type: "components" },
|
|
637
|
+
{ dirs: ["handlers", "src/handlers"], prefix: "#", type: "components" },
|
|
638
|
+
{ dirs: ["controllers", "src/controllers"], prefix: "#", type: "components" },
|
|
639
|
+
{ dirs: ["components", "src/components"], prefix: "#", type: "components" },
|
|
640
|
+
{ dirs: ["lib", "src/lib"], prefix: "#", type: "components" },
|
|
641
|
+
{ dirs: ["middleware", "src/middleware"], prefix: "^", type: "gates" },
|
|
642
|
+
{ dirs: ["guards", "src/guards"], prefix: "^", type: "gates" },
|
|
643
|
+
{ dirs: ["auth", "src/auth"], prefix: "^", type: "gates" },
|
|
644
|
+
{ dirs: ["events", "src/events"], prefix: "!", type: "signals" },
|
|
645
|
+
{ dirs: ["listeners", "src/listeners"], prefix: "!", type: "signals" },
|
|
646
|
+
{ dirs: ["flows", "src/flows"], prefix: "$", type: "flows" },
|
|
647
|
+
{ dirs: ["workflows", "src/workflows"], prefix: "$", type: "flows" },
|
|
648
|
+
{ dirs: ["pipelines", "src/pipelines"], prefix: "$", type: "flows" }
|
|
649
|
+
];
|
|
650
|
+
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".js", ".tsx", ".jsx", ".mjs", ".mts"]);
|
|
651
|
+
function detectSymbols(projectDir) {
|
|
652
|
+
const result = {
|
|
653
|
+
components: [],
|
|
654
|
+
gates: [],
|
|
655
|
+
flows: [],
|
|
656
|
+
signals: [],
|
|
657
|
+
routes: {}
|
|
658
|
+
};
|
|
659
|
+
const purposeSymbols = readPurposeFiles(projectDir);
|
|
660
|
+
if (purposeSymbols) {
|
|
661
|
+
result.components.push(...purposeSymbols.components);
|
|
662
|
+
result.gates.push(...purposeSymbols.gates);
|
|
663
|
+
result.flows.push(...purposeSymbols.flows);
|
|
664
|
+
result.signals.push(...purposeSymbols.signals);
|
|
665
|
+
}
|
|
666
|
+
for (const pattern of DIR_PATTERNS) {
|
|
667
|
+
for (const dir of pattern.dirs) {
|
|
668
|
+
const fullPath = path3.join(projectDir, dir);
|
|
669
|
+
if (!fs3.existsSync(fullPath)) continue;
|
|
670
|
+
const files = safeReaddir(fullPath);
|
|
671
|
+
for (const file of files) {
|
|
672
|
+
const ext = path3.extname(file);
|
|
673
|
+
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
674
|
+
const name = path3.basename(file, ext);
|
|
675
|
+
if (name === "index" || name.endsWith(".test") || name.endsWith(".spec")) continue;
|
|
676
|
+
const symbol = `${pattern.prefix}${toKebabCase(name)}`;
|
|
677
|
+
if (!result[pattern.type].includes(symbol)) {
|
|
678
|
+
result[pattern.type].push(symbol);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
scanRoutes(projectDir, result);
|
|
684
|
+
return result;
|
|
685
|
+
}
|
|
686
|
+
function generateConfig(projectDir) {
|
|
687
|
+
const detected = detectSymbols(projectDir);
|
|
688
|
+
return {
|
|
689
|
+
version: "1.0",
|
|
690
|
+
project: path3.basename(projectDir),
|
|
691
|
+
symbols: {
|
|
692
|
+
components: detected.components.length > 0 ? detected.components : void 0,
|
|
693
|
+
gates: detected.gates.length > 0 ? detected.gates : void 0,
|
|
694
|
+
flows: detected.flows.length > 0 ? detected.flows : void 0,
|
|
695
|
+
signals: detected.signals.length > 0 ? detected.signals : void 0
|
|
696
|
+
},
|
|
697
|
+
routes: Object.keys(detected.routes).length > 0 ? detected.routes : void 0
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function readPurposeFiles(projectDir) {
|
|
701
|
+
const paradigmDir = path3.join(projectDir, ".paradigm");
|
|
702
|
+
if (!fs3.existsSync(paradigmDir)) return null;
|
|
703
|
+
const result = {
|
|
704
|
+
components: [],
|
|
705
|
+
gates: [],
|
|
706
|
+
flows: [],
|
|
707
|
+
signals: [],
|
|
708
|
+
routes: {}
|
|
709
|
+
};
|
|
710
|
+
const purposeFiles = findFiles(projectDir, ".purpose");
|
|
711
|
+
for (const file of purposeFiles) {
|
|
712
|
+
try {
|
|
713
|
+
const content = fs3.readFileSync(file, "utf-8");
|
|
714
|
+
extractPurposeSymbols(content, result);
|
|
715
|
+
} catch {
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const hasAny = result.components.length > 0 || result.gates.length > 0 || result.flows.length > 0 || result.signals.length > 0;
|
|
719
|
+
return hasAny ? result : null;
|
|
720
|
+
}
|
|
721
|
+
function extractPurposeSymbols(content, result) {
|
|
722
|
+
const lines = content.split("\n");
|
|
723
|
+
let currentSection = "";
|
|
724
|
+
for (const line of lines) {
|
|
725
|
+
const trimmed = line.trim();
|
|
726
|
+
if (trimmed === "components:" || trimmed === "features:") {
|
|
727
|
+
currentSection = "components";
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (trimmed === "gates:") {
|
|
731
|
+
currentSection = "gates";
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (trimmed === "flows:") {
|
|
735
|
+
currentSection = "flows";
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (trimmed === "signals:") {
|
|
739
|
+
currentSection = "signals";
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
if (currentSection && /^\s{2}\S/.test(line)) {
|
|
743
|
+
const idMatch = trimmed.match(/^([a-zA-Z][\w-]*):$/);
|
|
744
|
+
if (idMatch) {
|
|
745
|
+
const prefixes = {
|
|
746
|
+
components: "#",
|
|
747
|
+
gates: "^",
|
|
748
|
+
flows: "$",
|
|
749
|
+
signals: "!"
|
|
750
|
+
};
|
|
751
|
+
const prefix = prefixes[currentSection] || "#";
|
|
752
|
+
const symbol = `${prefix}${idMatch[1]}`;
|
|
753
|
+
if (!result[currentSection]?.includes(symbol)) {
|
|
754
|
+
result[currentSection]?.push(symbol);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (trimmed && !line.startsWith(" ") && !trimmed.endsWith(":")) {
|
|
759
|
+
currentSection = "";
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function scanRoutes(projectDir, result) {
|
|
764
|
+
const routeDirs = ["routes", "src/routes", "api", "src/api"];
|
|
765
|
+
for (const dir of routeDirs) {
|
|
766
|
+
const fullPath = path3.join(projectDir, dir);
|
|
767
|
+
if (!fs3.existsSync(fullPath)) continue;
|
|
768
|
+
const files = safeReaddir(fullPath);
|
|
769
|
+
for (const file of files) {
|
|
770
|
+
const ext = path3.extname(file);
|
|
771
|
+
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
772
|
+
const name = path3.basename(file, ext);
|
|
773
|
+
if (name === "index") continue;
|
|
774
|
+
const routePrefix = `/api/${toKebabCase(name)}`;
|
|
775
|
+
const component = `#${toKebabCase(name)}`;
|
|
776
|
+
result.routes[routePrefix] = component;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
function toKebabCase(str) {
|
|
781
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").replace(/\..*$/, "").toLowerCase();
|
|
782
|
+
}
|
|
783
|
+
function safeReaddir(dir) {
|
|
784
|
+
try {
|
|
785
|
+
return fs3.readdirSync(dir).filter((f) => {
|
|
786
|
+
const fullPath = path3.join(dir, f);
|
|
787
|
+
try {
|
|
788
|
+
return fs3.statSync(fullPath).isFile();
|
|
789
|
+
} catch {
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
} catch {
|
|
794
|
+
return [];
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function findFiles(dir, filename, maxDepth = 4, depth = 0) {
|
|
798
|
+
if (depth > maxDepth) return [];
|
|
799
|
+
const results = [];
|
|
800
|
+
const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".next", ".nuxt"]);
|
|
801
|
+
try {
|
|
802
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
803
|
+
for (const entry of entries) {
|
|
804
|
+
if (entry.isFile() && entry.name === filename) {
|
|
805
|
+
results.push(path3.join(dir, entry.name));
|
|
806
|
+
} else if (entry.isDirectory() && !skipDirs.has(entry.name)) {
|
|
807
|
+
results.push(...findFiles(path3.join(dir, entry.name), filename, maxDepth, depth + 1));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
812
|
+
return results;
|
|
813
|
+
}
|
|
814
|
+
var SIMILARITY_THRESHOLD = 0.6;
|
|
815
|
+
var IncidentGrouper = class {
|
|
816
|
+
constructor(storage) {
|
|
817
|
+
this.storage = storage;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Try to find or create a group for an incident
|
|
821
|
+
* Returns the group ID if grouped, null if no suitable group
|
|
822
|
+
*/
|
|
823
|
+
group(incident) {
|
|
824
|
+
const groups = this.storage.getGroups({ limit: 100 });
|
|
825
|
+
for (const group of groups) {
|
|
826
|
+
if (this.shouldJoinGroup(incident, group)) {
|
|
827
|
+
this.storage.addToGroup(group.id, incident.id);
|
|
828
|
+
return group.id;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
const similar = this.findSimilar(incident, 10);
|
|
832
|
+
if (similar.length >= 1) {
|
|
833
|
+
const commonSymbols = this.extractCommonSymbols([incident, ...similar]);
|
|
834
|
+
const commonErrorPatterns = this.extractCommonErrorPatterns([
|
|
835
|
+
incident,
|
|
836
|
+
...similar
|
|
837
|
+
]);
|
|
838
|
+
const groupId = this.storage.createGroup({
|
|
839
|
+
incidents: [incident.id, ...similar.map((i) => i.id)],
|
|
840
|
+
commonSymbols,
|
|
841
|
+
commonErrorPatterns,
|
|
842
|
+
firstSeen: this.getEarliestTimestamp([incident, ...similar]),
|
|
843
|
+
lastSeen: incident.timestamp,
|
|
844
|
+
environments: this.getUniqueEnvironments([incident, ...similar])
|
|
845
|
+
});
|
|
846
|
+
return groupId;
|
|
847
|
+
}
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Find incidents similar to the given one
|
|
852
|
+
*/
|
|
853
|
+
findSimilar(incident, limit = 10) {
|
|
854
|
+
const candidates = this.storage.getRecentIncidents({
|
|
855
|
+
limit: 500,
|
|
856
|
+
status: "all"
|
|
857
|
+
});
|
|
858
|
+
const similar = [];
|
|
859
|
+
for (const candidate of candidates) {
|
|
860
|
+
if (candidate.id === incident.id) {
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
const score = this.calculateSimilarity(incident, candidate);
|
|
864
|
+
if (score >= SIMILARITY_THRESHOLD) {
|
|
865
|
+
similar.push({ incident: candidate, score });
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return similar.sort((a, b) => b.score - a.score).slice(0, limit).map((s) => s.incident);
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Analyze ungrouped incidents and create groups automatically
|
|
872
|
+
*/
|
|
873
|
+
analyzeAndGroup(options = {}) {
|
|
874
|
+
const minSize = options.minSize || 3;
|
|
875
|
+
const ungrouped = this.storage.getRecentIncidents({
|
|
876
|
+
limit: 1e3
|
|
877
|
+
}).filter((i) => !i.groupId);
|
|
878
|
+
const newGroups = [];
|
|
879
|
+
const processed = /* @__PURE__ */ new Set();
|
|
880
|
+
for (const incident of ungrouped) {
|
|
881
|
+
if (processed.has(incident.id)) {
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
const similar = ungrouped.filter(
|
|
885
|
+
(other) => other.id !== incident.id && !processed.has(other.id) && this.calculateSimilarity(incident, other) >= SIMILARITY_THRESHOLD
|
|
886
|
+
);
|
|
887
|
+
if (similar.length + 1 >= minSize) {
|
|
888
|
+
const members = [incident, ...similar];
|
|
889
|
+
const commonSymbols = this.extractCommonSymbols(members);
|
|
890
|
+
const commonErrorPatterns = this.extractCommonErrorPatterns(members);
|
|
891
|
+
const groupId = this.storage.createGroup({
|
|
892
|
+
incidents: members.map((m) => m.id),
|
|
893
|
+
commonSymbols,
|
|
894
|
+
commonErrorPatterns,
|
|
895
|
+
firstSeen: this.getEarliestTimestamp(members),
|
|
896
|
+
lastSeen: this.getLatestTimestamp(members),
|
|
897
|
+
environments: this.getUniqueEnvironments(members)
|
|
898
|
+
});
|
|
899
|
+
for (const m of members) {
|
|
900
|
+
processed.add(m.id);
|
|
901
|
+
}
|
|
902
|
+
const group = this.storage.getGroup(groupId);
|
|
903
|
+
if (group) {
|
|
904
|
+
newGroups.push(group);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return newGroups;
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Calculate similarity between two incidents (0-1)
|
|
912
|
+
*/
|
|
913
|
+
calculateSimilarity(a, b) {
|
|
914
|
+
let score = 0;
|
|
915
|
+
let maxScore = 0;
|
|
916
|
+
const symbolWeight = 0.6;
|
|
917
|
+
const symbolTypes = [
|
|
918
|
+
"feature",
|
|
919
|
+
"component",
|
|
920
|
+
"flow",
|
|
921
|
+
"gate",
|
|
922
|
+
"signal",
|
|
923
|
+
"state",
|
|
924
|
+
"integration"
|
|
925
|
+
];
|
|
926
|
+
for (const type of symbolTypes) {
|
|
927
|
+
const aValue = a.symbols[type];
|
|
928
|
+
const bValue = b.symbols[type];
|
|
929
|
+
if (aValue || bValue) {
|
|
930
|
+
maxScore += symbolWeight / symbolTypes.length;
|
|
931
|
+
if (aValue === bValue) {
|
|
932
|
+
score += symbolWeight / symbolTypes.length;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const errorWeight = 0.3;
|
|
937
|
+
const errorSimilarity = this.stringSimilarity(
|
|
938
|
+
a.error.message,
|
|
939
|
+
b.error.message
|
|
940
|
+
);
|
|
941
|
+
score += errorWeight * errorSimilarity;
|
|
942
|
+
maxScore += errorWeight;
|
|
943
|
+
const envWeight = 0.1;
|
|
944
|
+
if (a.environment === b.environment) {
|
|
945
|
+
score += envWeight;
|
|
946
|
+
}
|
|
947
|
+
maxScore += envWeight;
|
|
948
|
+
return maxScore > 0 ? score / maxScore : 0;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Calculate string similarity using Levenshtein distance
|
|
952
|
+
*/
|
|
953
|
+
stringSimilarity(a, b) {
|
|
954
|
+
const maxLen = Math.max(a.length, b.length);
|
|
955
|
+
if (maxLen === 0) return 1;
|
|
956
|
+
const distance = this.levenshteinDistance(
|
|
957
|
+
a.toLowerCase(),
|
|
958
|
+
b.toLowerCase()
|
|
959
|
+
);
|
|
960
|
+
return 1 - distance / maxLen;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Levenshtein distance for string comparison
|
|
964
|
+
*/
|
|
965
|
+
levenshteinDistance(a, b) {
|
|
966
|
+
if (a.length === 0) return b.length;
|
|
967
|
+
if (b.length === 0) return a.length;
|
|
968
|
+
const matrix = [];
|
|
969
|
+
for (let i = 0; i <= b.length; i++) {
|
|
970
|
+
matrix[i] = [i];
|
|
971
|
+
}
|
|
972
|
+
for (let j = 0; j <= a.length; j++) {
|
|
973
|
+
matrix[0][j] = j;
|
|
974
|
+
}
|
|
975
|
+
for (let i = 1; i <= b.length; i++) {
|
|
976
|
+
for (let j = 1; j <= a.length; j++) {
|
|
977
|
+
const cost = a[j - 1] === b[i - 1] ? 0 : 1;
|
|
978
|
+
matrix[i][j] = Math.min(
|
|
979
|
+
matrix[i - 1][j] + 1,
|
|
980
|
+
matrix[i][j - 1] + 1,
|
|
981
|
+
matrix[i - 1][j - 1] + cost
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return matrix[b.length][a.length];
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Check if incident should join existing group
|
|
989
|
+
*/
|
|
990
|
+
shouldJoinGroup(incident, group) {
|
|
991
|
+
let matchCount = 0;
|
|
992
|
+
let totalCommon = 0;
|
|
993
|
+
for (const [key, value] of Object.entries(group.commonSymbols)) {
|
|
994
|
+
if (value) {
|
|
995
|
+
totalCommon++;
|
|
996
|
+
const incidentValue = incident.symbols[key];
|
|
997
|
+
if (incidentValue === value) {
|
|
998
|
+
matchCount++;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (totalCommon === 0) {
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
const symbolMatch = matchCount / totalCommon;
|
|
1006
|
+
const errorLower = incident.error.message.toLowerCase();
|
|
1007
|
+
const errorMatch = group.commonErrorPatterns.some(
|
|
1008
|
+
(pattern) => errorLower.includes(pattern.toLowerCase())
|
|
1009
|
+
);
|
|
1010
|
+
return symbolMatch >= 0.5 || errorMatch;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Extract symbols common to all incidents
|
|
1014
|
+
*/
|
|
1015
|
+
extractCommonSymbols(incidents) {
|
|
1016
|
+
if (incidents.length === 0) return {};
|
|
1017
|
+
const first = incidents[0].symbols;
|
|
1018
|
+
const common = {};
|
|
1019
|
+
for (const [key, value] of Object.entries(first)) {
|
|
1020
|
+
if (!value) continue;
|
|
1021
|
+
const allMatch = incidents.every(
|
|
1022
|
+
(i) => i.symbols[key] === value
|
|
1023
|
+
);
|
|
1024
|
+
if (allMatch) {
|
|
1025
|
+
common[key] = value;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return common;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Extract common error patterns from incidents
|
|
1032
|
+
*/
|
|
1033
|
+
extractCommonErrorPatterns(incidents) {
|
|
1034
|
+
if (incidents.length === 0) return [];
|
|
1035
|
+
const wordCounts = /* @__PURE__ */ new Map();
|
|
1036
|
+
const stopWords = /* @__PURE__ */ new Set([
|
|
1037
|
+
"the",
|
|
1038
|
+
"a",
|
|
1039
|
+
"an",
|
|
1040
|
+
"is",
|
|
1041
|
+
"are",
|
|
1042
|
+
"was",
|
|
1043
|
+
"were",
|
|
1044
|
+
"in",
|
|
1045
|
+
"on",
|
|
1046
|
+
"at",
|
|
1047
|
+
"to",
|
|
1048
|
+
"for",
|
|
1049
|
+
"of",
|
|
1050
|
+
"with",
|
|
1051
|
+
"error",
|
|
1052
|
+
"failed",
|
|
1053
|
+
"cannot"
|
|
1054
|
+
]);
|
|
1055
|
+
for (const incident of incidents) {
|
|
1056
|
+
const words = incident.error.message.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
|
|
1057
|
+
const uniqueWords = new Set(words);
|
|
1058
|
+
for (const word of uniqueWords) {
|
|
1059
|
+
wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
const threshold = Math.ceil(incidents.length * 0.6);
|
|
1063
|
+
const commonPatterns = Array.from(wordCounts.entries()).filter(([, count]) => count >= threshold).map(([word]) => word).slice(0, 5);
|
|
1064
|
+
return commonPatterns;
|
|
1065
|
+
}
|
|
1066
|
+
getEarliestTimestamp(incidents) {
|
|
1067
|
+
return incidents.reduce(
|
|
1068
|
+
(earliest, i) => i.timestamp < earliest ? i.timestamp : earliest,
|
|
1069
|
+
incidents[0].timestamp
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
getLatestTimestamp(incidents) {
|
|
1073
|
+
return incidents.reduce(
|
|
1074
|
+
(latest, i) => i.timestamp > latest ? i.timestamp : latest,
|
|
1075
|
+
incidents[0].timestamp
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
getUniqueEnvironments(incidents) {
|
|
1079
|
+
return [...new Set(incidents.map((i) => i.environment))];
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
256
1082
|
var TimelineBuilder = class {
|
|
257
1083
|
/**
|
|
258
1084
|
* Build a timeline from an incident with flow position
|
|
@@ -426,8 +1252,8 @@ var TimelineBuilder = class {
|
|
|
426
1252
|
}
|
|
427
1253
|
};
|
|
428
1254
|
var StatsCalculator = class {
|
|
429
|
-
constructor(
|
|
430
|
-
this.storage =
|
|
1255
|
+
constructor(storage) {
|
|
1256
|
+
this.storage = storage;
|
|
431
1257
|
}
|
|
432
1258
|
/**
|
|
433
1259
|
* Get comprehensive statistics for a time period
|
|
@@ -653,9 +1479,190 @@ var StatsCalculator = class {
|
|
|
653
1479
|
return symbols;
|
|
654
1480
|
}
|
|
655
1481
|
};
|
|
1482
|
+
var ContextEnricher = class {
|
|
1483
|
+
constructor(projectRoot = process.cwd()) {
|
|
1484
|
+
this.projectRoot = projectRoot;
|
|
1485
|
+
}
|
|
1486
|
+
symbolCache = /* @__PURE__ */ new Map();
|
|
1487
|
+
purposeCache = /* @__PURE__ */ new Map();
|
|
1488
|
+
/**
|
|
1489
|
+
* Enrich an incident with symbol context
|
|
1490
|
+
*/
|
|
1491
|
+
enrich(incident) {
|
|
1492
|
+
const symbolEnrichments = {};
|
|
1493
|
+
for (const [, value] of Object.entries(incident.symbols)) {
|
|
1494
|
+
if (value) {
|
|
1495
|
+
const enrichment = this.getSymbolContext(value);
|
|
1496
|
+
if (enrichment && Object.keys(enrichment).length > 0) {
|
|
1497
|
+
symbolEnrichments[value] = enrichment;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
let flowDescription;
|
|
1502
|
+
if (incident.symbols.flow) {
|
|
1503
|
+
const flowContext = this.getSymbolContext(incident.symbols.flow);
|
|
1504
|
+
flowDescription = flowContext?.description;
|
|
1505
|
+
}
|
|
1506
|
+
return {
|
|
1507
|
+
...incident,
|
|
1508
|
+
enriched: {
|
|
1509
|
+
symbols: symbolEnrichments,
|
|
1510
|
+
flowDescription
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Get symbol metadata from index or .purpose files
|
|
1516
|
+
*/
|
|
1517
|
+
getSymbolContext(symbol) {
|
|
1518
|
+
const cached = this.symbolCache.get(symbol);
|
|
1519
|
+
if (cached) {
|
|
1520
|
+
return cached;
|
|
1521
|
+
}
|
|
1522
|
+
const enrichment = {};
|
|
1523
|
+
const indexEntry = this.findInSymbolIndex(symbol);
|
|
1524
|
+
if (indexEntry) {
|
|
1525
|
+
enrichment.description = indexEntry.description;
|
|
1526
|
+
enrichment.definedIn = indexEntry.file;
|
|
1527
|
+
enrichment.references = indexEntry.references;
|
|
1528
|
+
enrichment.referencedBy = indexEntry.referencedBy;
|
|
1529
|
+
}
|
|
1530
|
+
const purposeEntry = this.findInPurposeFiles(symbol);
|
|
1531
|
+
if (purposeEntry) {
|
|
1532
|
+
if (!enrichment.description && purposeEntry.description) {
|
|
1533
|
+
enrichment.description = purposeEntry.description;
|
|
1534
|
+
}
|
|
1535
|
+
if (purposeEntry.references) {
|
|
1536
|
+
enrichment.references = [
|
|
1537
|
+
.../* @__PURE__ */ new Set([...enrichment.references || [], ...purposeEntry.references])
|
|
1538
|
+
];
|
|
1539
|
+
}
|
|
1540
|
+
if (purposeEntry.referencedBy) {
|
|
1541
|
+
enrichment.referencedBy = [
|
|
1542
|
+
.../* @__PURE__ */ new Set([...enrichment.referencedBy || [], ...purposeEntry.referencedBy])
|
|
1543
|
+
];
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
this.symbolCache.set(symbol, enrichment);
|
|
1547
|
+
return enrichment;
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Find symbol in premise index
|
|
1551
|
+
*/
|
|
1552
|
+
findInSymbolIndex(symbol) {
|
|
1553
|
+
const indexPath = path4.join(this.projectRoot, ".paradigm", "index.json");
|
|
1554
|
+
if (!fs4.existsSync(indexPath)) {
|
|
1555
|
+
return null;
|
|
1556
|
+
}
|
|
1557
|
+
try {
|
|
1558
|
+
const indexContent = fs4.readFileSync(indexPath, "utf-8");
|
|
1559
|
+
const index = JSON.parse(indexContent);
|
|
1560
|
+
if (index.symbols && Array.isArray(index.symbols)) {
|
|
1561
|
+
return index.symbols.find(
|
|
1562
|
+
(s) => s.id === symbol
|
|
1563
|
+
) || null;
|
|
1564
|
+
}
|
|
1565
|
+
return null;
|
|
1566
|
+
} catch {
|
|
1567
|
+
return null;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Find symbol in .purpose files
|
|
1572
|
+
*/
|
|
1573
|
+
findInPurposeFiles(symbol) {
|
|
1574
|
+
const searchPaths = this.getSearchPathsForSymbol(symbol);
|
|
1575
|
+
for (const searchPath of searchPaths) {
|
|
1576
|
+
const fullPath = path4.join(this.projectRoot, searchPath);
|
|
1577
|
+
if (!fs4.existsSync(fullPath)) {
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
const cached = this.purposeCache.get(fullPath);
|
|
1581
|
+
if (cached) {
|
|
1582
|
+
if (cached.symbol === symbol) {
|
|
1583
|
+
return cached;
|
|
1584
|
+
}
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
try {
|
|
1588
|
+
const content = fs4.readFileSync(fullPath, "utf-8");
|
|
1589
|
+
const purpose = this.parsePurposeFile(content);
|
|
1590
|
+
this.purposeCache.set(fullPath, purpose);
|
|
1591
|
+
if (purpose.symbol === symbol) {
|
|
1592
|
+
return purpose;
|
|
1593
|
+
}
|
|
1594
|
+
} catch {
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
return null;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Get potential file paths for a symbol
|
|
1602
|
+
*/
|
|
1603
|
+
getSearchPathsForSymbol(symbol) {
|
|
1604
|
+
const paths = [];
|
|
1605
|
+
const cleanSymbol = symbol.replace(/^[@#$%^!&~?]/, "");
|
|
1606
|
+
const prefixDirs = {
|
|
1607
|
+
"@": ["features", "src/features"],
|
|
1608
|
+
"#": ["components", "src/components"],
|
|
1609
|
+
"$": ["flows", "src/flows"],
|
|
1610
|
+
"^": ["middleware", "gates", "src/middleware"],
|
|
1611
|
+
"!": ["signals", "events", "src/signals"],
|
|
1612
|
+
"%": ["state", "store", "src/state"],
|
|
1613
|
+
"&": ["integrations", "services", "src/integrations"]
|
|
1614
|
+
};
|
|
1615
|
+
const prefix = symbol[0];
|
|
1616
|
+
const dirs = prefixDirs[prefix] || [];
|
|
1617
|
+
for (const dir of dirs) {
|
|
1618
|
+
paths.push(path4.join(dir, cleanSymbol, ".purpose"));
|
|
1619
|
+
paths.push(path4.join(dir, `${cleanSymbol}.purpose`));
|
|
1620
|
+
}
|
|
1621
|
+
paths.push(path4.join(".paradigm", "purposes", `${cleanSymbol}.yaml`));
|
|
1622
|
+
paths.push(path4.join(".paradigm", "purposes", `${cleanSymbol}.json`));
|
|
1623
|
+
return paths;
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Parse a .purpose file
|
|
1627
|
+
*/
|
|
1628
|
+
parsePurposeFile(content) {
|
|
1629
|
+
const result = {};
|
|
1630
|
+
const lines = content.split("\n");
|
|
1631
|
+
for (const line of lines) {
|
|
1632
|
+
const trimmed = line.trim();
|
|
1633
|
+
if (trimmed.startsWith("symbol:")) {
|
|
1634
|
+
result.symbol = trimmed.substring(7).trim();
|
|
1635
|
+
} else if (trimmed.startsWith("description:")) {
|
|
1636
|
+
result.description = trimmed.substring(12).trim();
|
|
1637
|
+
} else if (trimmed.startsWith("purpose:")) {
|
|
1638
|
+
result.description = trimmed.substring(8).trim();
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (!result.description) {
|
|
1642
|
+
const firstLine = lines.find((l) => l.trim() && !l.startsWith("#"));
|
|
1643
|
+
if (firstLine) {
|
|
1644
|
+
result.description = firstLine.trim();
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
return result;
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Clear caches
|
|
1651
|
+
*/
|
|
1652
|
+
clearCache() {
|
|
1653
|
+
this.symbolCache.clear();
|
|
1654
|
+
this.purposeCache.clear();
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Batch enrich multiple incidents
|
|
1658
|
+
*/
|
|
1659
|
+
enrichBatch(incidents) {
|
|
1660
|
+
return incidents.map((i) => this.enrich(i));
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
656
1663
|
var PatternSuggester = class {
|
|
657
|
-
constructor(
|
|
658
|
-
this.storage =
|
|
1664
|
+
constructor(storage) {
|
|
1665
|
+
this.storage = storage;
|
|
659
1666
|
}
|
|
660
1667
|
/**
|
|
661
1668
|
* Suggest a pattern from a resolved incident
|
|
@@ -1224,656 +2231,21 @@ var PatternImporter = class {
|
|
|
1224
2231
|
}
|
|
1225
2232
|
};
|
|
1226
2233
|
|
|
1227
|
-
// src/commands/triage/utils/format.ts
|
|
1228
|
-
import chalk from "chalk";
|
|
1229
|
-
function formatHeader() {
|
|
1230
|
-
return `
|
|
1231
|
-
${chalk.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}
|
|
1232
|
-
${chalk.cyan("\u2551")} ${chalk.bold.white("PARADIGM SENTINEL TRIAGE")} ${chalk.cyan("\u2551")}
|
|
1233
|
-
${chalk.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
|
|
1234
|
-
`;
|
|
1235
|
-
}
|
|
1236
|
-
function formatSummaryBar(stats) {
|
|
1237
|
-
return `${chalk.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}
|
|
1238
|
-
${chalk.cyan("\u2551")} Open: ${chalk.yellow(String(stats.open).padEnd(4))} \u2502 Investigating: ${chalk.blue(String(stats.investigating).padEnd(3))} \u2502 Resolved: ${chalk.green(String(stats.resolved).padEnd(4))} \u2502 Today: ${chalk.magenta(`+${stats.today}`)} ${chalk.cyan("\u2551")}
|
|
1239
|
-
${chalk.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
|
|
1240
|
-
`;
|
|
1241
|
-
}
|
|
1242
|
-
function formatIncident(incident, matches) {
|
|
1243
|
-
const lines = [];
|
|
1244
|
-
const statusColor = getStatusColor(incident.status);
|
|
1245
|
-
lines.push(
|
|
1246
|
-
chalk.gray("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")
|
|
1247
|
-
);
|
|
1248
|
-
lines.push(
|
|
1249
|
-
chalk.gray("\u2502 ") + chalk.bold(`[${incident.id}] `) + statusColor(incident.status.toUpperCase().padEnd(12)) + chalk.gray(incident.timestamp.substring(0, 19).replace("T", " ").padStart(19)) + chalk.gray(" \u2502")
|
|
1250
|
-
);
|
|
1251
|
-
lines.push(
|
|
1252
|
-
chalk.gray("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524")
|
|
1253
|
-
);
|
|
1254
|
-
lines.push(chalk.gray("\u2502 ") + chalk.red("Error: ") + truncate(incident.error.message, 55) + chalk.gray(" \u2502"));
|
|
1255
|
-
lines.push(chalk.gray("\u2502") + " ".repeat(65) + chalk.gray("\u2502"));
|
|
1256
|
-
lines.push(chalk.gray("\u2502 ") + chalk.cyan("Symbolic Context:") + " ".repeat(47) + chalk.gray("\u2502"));
|
|
1257
|
-
const symbols = formatSymbols(incident.symbols);
|
|
1258
|
-
for (const sym of symbols) {
|
|
1259
|
-
lines.push(chalk.gray("\u2502 ") + sym.padEnd(61) + chalk.gray(" \u2502"));
|
|
1260
|
-
}
|
|
1261
|
-
lines.push(chalk.gray("\u2502") + " ".repeat(65) + chalk.gray("\u2502"));
|
|
1262
|
-
const envLine = `Environment: ${chalk.yellow(incident.environment)} \u2502 Service: ${chalk.yellow(incident.service || "N/A")} \u2502 v${incident.version || "N/A"}`;
|
|
1263
|
-
lines.push(chalk.gray("\u2502 ") + envLine.substring(0, 63).padEnd(63) + chalk.gray(" \u2502"));
|
|
1264
|
-
if (matches && matches.length > 0) {
|
|
1265
|
-
lines.push(chalk.gray("\u2502") + " ".repeat(65) + chalk.gray("\u2502"));
|
|
1266
|
-
lines.push(
|
|
1267
|
-
chalk.gray("\u2502 \u250C\u2500 ") + chalk.cyan("Matched Patterns") + chalk.gray(" \u2500".repeat(22) + "\u2510 \u2502")
|
|
1268
|
-
);
|
|
1269
|
-
for (let i = 0; i < Math.min(matches.length, 3); i++) {
|
|
1270
|
-
const match = matches[i];
|
|
1271
|
-
const icon = i === 0 ? chalk.yellow("\u2605") : chalk.gray("\u25CB");
|
|
1272
|
-
const conf = `${match.confidence}% confidence`;
|
|
1273
|
-
lines.push(
|
|
1274
|
-
chalk.gray("\u2502 \u2502 ") + icon + " " + chalk.bold(truncate(match.pattern.id, 30).padEnd(30)) + chalk.gray(conf.padStart(15)) + chalk.gray(" \u2502 \u2502")
|
|
1275
|
-
);
|
|
1276
|
-
lines.push(
|
|
1277
|
-
chalk.gray("\u2502 \u2502 ") + chalk.italic(truncate(match.pattern.description, 45).padEnd(45)) + chalk.gray(" \u2502 \u2502")
|
|
1278
|
-
);
|
|
1279
|
-
lines.push(
|
|
1280
|
-
chalk.gray("\u2502 \u2502 Strategy: ") + chalk.cyan(match.pattern.resolution.strategy.padEnd(40)) + chalk.gray(" \u2502 \u2502")
|
|
1281
|
-
);
|
|
1282
|
-
if (i < Math.min(matches.length, 3) - 1) {
|
|
1283
|
-
lines.push(chalk.gray("\u2502 \u2502") + " ".repeat(59) + chalk.gray("\u2502 \u2502"));
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
lines.push(
|
|
1287
|
-
chalk.gray("\u2502 \u2514") + "\u2500".repeat(59) + chalk.gray("\u2518 \u2502")
|
|
1288
|
-
);
|
|
1289
|
-
}
|
|
1290
|
-
lines.push(chalk.gray("\u2502") + " ".repeat(65) + chalk.gray("\u2502"));
|
|
1291
|
-
lines.push(chalk.gray("\u2502 ") + chalk.dim("Actions:") + " ".repeat(56) + chalk.gray("\u2502"));
|
|
1292
|
-
lines.push(
|
|
1293
|
-
chalk.gray("\u2502 ") + chalk.dim(`paradigm triage resolve ${incident.id}`) + " ".repeat(35 - incident.id.length) + chalk.gray("\u2502")
|
|
1294
|
-
);
|
|
1295
|
-
lines.push(
|
|
1296
|
-
chalk.gray("\u2502 ") + chalk.dim(`paradigm triage show ${incident.id} --timeline`) + " ".repeat(28 - incident.id.length) + chalk.gray("\u2502")
|
|
1297
|
-
);
|
|
1298
|
-
lines.push(
|
|
1299
|
-
chalk.gray("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")
|
|
1300
|
-
);
|
|
1301
|
-
return lines.join("\n");
|
|
1302
|
-
}
|
|
1303
|
-
function formatIncidentCompact(incident) {
|
|
1304
|
-
const statusColor = getStatusColor(incident.status);
|
|
1305
|
-
const status = statusColor(incident.status.substring(0, 4).toUpperCase().padEnd(4));
|
|
1306
|
-
const timestamp = incident.timestamp.substring(5, 16).replace("T", " ");
|
|
1307
|
-
const error = truncate(incident.error.message, 40);
|
|
1308
|
-
const symbols = Object.values(incident.symbols).filter(Boolean).join(" ");
|
|
1309
|
-
return `${chalk.bold(incident.id)} ${status} ${chalk.gray(timestamp)} ${error}
|
|
1310
|
-
${chalk.cyan(truncate(symbols, 60))}`;
|
|
1311
|
-
}
|
|
1312
|
-
function formatPattern(pattern) {
|
|
1313
|
-
const lines = [];
|
|
1314
|
-
lines.push(chalk.bold.cyan(`Pattern: ${pattern.id}`));
|
|
1315
|
-
lines.push(chalk.white(` Name: ${pattern.name}`));
|
|
1316
|
-
lines.push(chalk.gray(` Description: ${pattern.description}`));
|
|
1317
|
-
lines.push("");
|
|
1318
|
-
lines.push(chalk.yellow(" Matching Criteria:"));
|
|
1319
|
-
if (pattern.pattern.symbols) {
|
|
1320
|
-
for (const [key, value] of Object.entries(pattern.pattern.symbols)) {
|
|
1321
|
-
if (value) {
|
|
1322
|
-
const v = Array.isArray(value) ? value.join(", ") : value;
|
|
1323
|
-
lines.push(` ${chalk.cyan(key)}: ${v}`);
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
if (pattern.pattern.errorContains) {
|
|
1328
|
-
lines.push(
|
|
1329
|
-
` ${chalk.cyan("errorContains")}: ${pattern.pattern.errorContains.join(", ")}`
|
|
1330
|
-
);
|
|
1331
|
-
}
|
|
1332
|
-
if (pattern.pattern.missingSignals) {
|
|
1333
|
-
lines.push(
|
|
1334
|
-
` ${chalk.cyan("missingSignals")}: ${pattern.pattern.missingSignals.join(", ")}`
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1337
|
-
lines.push("");
|
|
1338
|
-
lines.push(chalk.green(" Resolution:"));
|
|
1339
|
-
lines.push(` ${chalk.white(pattern.resolution.description)}`);
|
|
1340
|
-
lines.push(
|
|
1341
|
-
` Strategy: ${chalk.cyan(pattern.resolution.strategy)} Priority: ${getPriorityColor(pattern.resolution.priority)(pattern.resolution.priority)}`
|
|
1342
|
-
);
|
|
1343
|
-
if (pattern.resolution.codeHint) {
|
|
1344
|
-
lines.push(` ${chalk.dim("Hint: " + pattern.resolution.codeHint)}`);
|
|
1345
|
-
}
|
|
1346
|
-
lines.push("");
|
|
1347
|
-
lines.push(chalk.blue(" Confidence:"));
|
|
1348
|
-
lines.push(
|
|
1349
|
-
` Score: ${getConfidenceColor(pattern.confidence.score)(pattern.confidence.score + "%")} Matched: ${pattern.confidence.timesMatched} Resolved: ${pattern.confidence.timesResolved} Recurred: ${pattern.confidence.timesRecurred}`
|
|
1350
|
-
);
|
|
1351
|
-
lines.push("");
|
|
1352
|
-
lines.push(
|
|
1353
|
-
chalk.gray(
|
|
1354
|
-
` Source: ${pattern.source} Tags: ${pattern.tags.join(", ") || "none"}`
|
|
1355
|
-
)
|
|
1356
|
-
);
|
|
1357
|
-
return lines.join("\n");
|
|
1358
|
-
}
|
|
1359
|
-
function formatPatternCompact(pattern) {
|
|
1360
|
-
const confidence = getConfidenceColor(pattern.confidence.score)(
|
|
1361
|
-
`${pattern.confidence.score}%`
|
|
1362
|
-
);
|
|
1363
|
-
const tags = pattern.tags.slice(0, 3).join(", ");
|
|
1364
|
-
return `${chalk.bold(pattern.id.padEnd(30))} ${confidence.padStart(8)} ${chalk.gray(pattern.source.padEnd(10))} ${chalk.dim(tags)}
|
|
1365
|
-
${chalk.white(truncate(pattern.name, 60))}`;
|
|
1366
|
-
}
|
|
1367
|
-
function formatSymbols(symbols) {
|
|
1368
|
-
const result = [];
|
|
1369
|
-
for (const [key, value] of Object.entries(symbols)) {
|
|
1370
|
-
if (value) {
|
|
1371
|
-
const color = getSymbolColor(key);
|
|
1372
|
-
result.push(`${color(value.padEnd(20))} ${chalk.dim(key)}`);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
return result;
|
|
1376
|
-
}
|
|
1377
|
-
function getStatusColor(status) {
|
|
1378
|
-
switch (status) {
|
|
1379
|
-
case "open":
|
|
1380
|
-
return chalk.red;
|
|
1381
|
-
case "investigating":
|
|
1382
|
-
return chalk.yellow;
|
|
1383
|
-
case "resolved":
|
|
1384
|
-
return chalk.green;
|
|
1385
|
-
case "wont-fix":
|
|
1386
|
-
return chalk.gray;
|
|
1387
|
-
default:
|
|
1388
|
-
return chalk.white;
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
function getSymbolColor(type) {
|
|
1392
|
-
switch (type) {
|
|
1393
|
-
case "feature":
|
|
1394
|
-
return chalk.magenta;
|
|
1395
|
-
case "component":
|
|
1396
|
-
return chalk.blue;
|
|
1397
|
-
case "flow":
|
|
1398
|
-
return chalk.cyan;
|
|
1399
|
-
case "gate":
|
|
1400
|
-
return chalk.yellow;
|
|
1401
|
-
case "signal":
|
|
1402
|
-
return chalk.green;
|
|
1403
|
-
case "state":
|
|
1404
|
-
return chalk.red;
|
|
1405
|
-
case "integration":
|
|
1406
|
-
return chalk.white;
|
|
1407
|
-
default:
|
|
1408
|
-
return chalk.gray;
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
function getPriorityColor(priority) {
|
|
1412
|
-
switch (priority) {
|
|
1413
|
-
case "critical":
|
|
1414
|
-
return chalk.red.bold;
|
|
1415
|
-
case "high":
|
|
1416
|
-
return chalk.red;
|
|
1417
|
-
case "medium":
|
|
1418
|
-
return chalk.yellow;
|
|
1419
|
-
case "low":
|
|
1420
|
-
return chalk.gray;
|
|
1421
|
-
default:
|
|
1422
|
-
return chalk.white;
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
function getConfidenceColor(score) {
|
|
1426
|
-
if (score >= 80) return chalk.green;
|
|
1427
|
-
if (score >= 60) return chalk.yellow;
|
|
1428
|
-
if (score >= 40) return chalk.red;
|
|
1429
|
-
return chalk.gray;
|
|
1430
|
-
}
|
|
1431
|
-
function truncate(str, maxLen) {
|
|
1432
|
-
if (str.length <= maxLen) return str;
|
|
1433
|
-
return str.substring(0, maxLen - 3) + "...";
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
// src/commands/triage/index.ts
|
|
1437
|
-
import * as fs2 from "fs";
|
|
1438
|
-
var storage = null;
|
|
1439
|
-
function getStorage() {
|
|
1440
|
-
if (!storage) {
|
|
1441
|
-
storage = new SentinelStorage();
|
|
1442
|
-
}
|
|
1443
|
-
return storage;
|
|
1444
|
-
}
|
|
1445
|
-
async function triageListCommand(options) {
|
|
1446
|
-
const store = getStorage();
|
|
1447
|
-
const matcher = new PatternMatcher(store);
|
|
1448
|
-
const limit = parseInt(options.limit || "10", 10);
|
|
1449
|
-
const status = options.status;
|
|
1450
|
-
const incidents = store.getRecentIncidents({
|
|
1451
|
-
limit,
|
|
1452
|
-
status: status || "all",
|
|
1453
|
-
symbol: options.symbol,
|
|
1454
|
-
environment: options.env,
|
|
1455
|
-
search: options.search,
|
|
1456
|
-
dateFrom: options.from,
|
|
1457
|
-
dateTo: options.to
|
|
1458
|
-
});
|
|
1459
|
-
if (options.json) {
|
|
1460
|
-
const result = incidents.map((i) => ({
|
|
1461
|
-
incident: i,
|
|
1462
|
-
matches: matcher.match(i, { maxResults: 3 })
|
|
1463
|
-
}));
|
|
1464
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1465
|
-
return;
|
|
1466
|
-
}
|
|
1467
|
-
const stats = new StatsCalculator(store).getStats(7);
|
|
1468
|
-
const todayStart = /* @__PURE__ */ new Date();
|
|
1469
|
-
todayStart.setHours(0, 0, 0, 0);
|
|
1470
|
-
const todayCount = store.getIncidentCount({
|
|
1471
|
-
dateFrom: todayStart.toISOString()
|
|
1472
|
-
});
|
|
1473
|
-
console.log(formatHeader());
|
|
1474
|
-
console.log(
|
|
1475
|
-
formatSummaryBar({
|
|
1476
|
-
open: stats.incidents.open,
|
|
1477
|
-
investigating: stats.incidents.total - stats.incidents.open - stats.incidents.resolved,
|
|
1478
|
-
resolved: stats.incidents.resolved,
|
|
1479
|
-
today: todayCount
|
|
1480
|
-
})
|
|
1481
|
-
);
|
|
1482
|
-
console.log("");
|
|
1483
|
-
if (incidents.length === 0) {
|
|
1484
|
-
console.log(chalk2.gray("No incidents found."));
|
|
1485
|
-
return;
|
|
1486
|
-
}
|
|
1487
|
-
for (const incident of incidents) {
|
|
1488
|
-
const matches = matcher.match(incident, { maxResults: 3 });
|
|
1489
|
-
console.log(formatIncident(incident, matches));
|
|
1490
|
-
console.log("");
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
async function triageShowCommand(incidentId, options) {
|
|
1494
|
-
const store = getStorage();
|
|
1495
|
-
const matcher = new PatternMatcher(store);
|
|
1496
|
-
const incident = store.getIncident(incidentId);
|
|
1497
|
-
if (!incident) {
|
|
1498
|
-
console.log(chalk2.red(`Incident ${incidentId} not found.`));
|
|
1499
|
-
return;
|
|
1500
|
-
}
|
|
1501
|
-
const matches = matcher.match(incident, { maxResults: 5 });
|
|
1502
|
-
if (options.json) {
|
|
1503
|
-
const result = { incident, matches };
|
|
1504
|
-
if (options.timeline && incident.flowPosition) {
|
|
1505
|
-
const timeline = new TimelineBuilder().build(incident);
|
|
1506
|
-
result.timeline = timeline ? new TimelineBuilder().renderStructured(timeline) : null;
|
|
1507
|
-
}
|
|
1508
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1509
|
-
return;
|
|
1510
|
-
}
|
|
1511
|
-
console.log(formatIncident(incident, matches));
|
|
1512
|
-
if (options.timeline && incident.flowPosition) {
|
|
1513
|
-
const timeline = new TimelineBuilder().build(incident);
|
|
1514
|
-
if (timeline) {
|
|
1515
|
-
console.log("");
|
|
1516
|
-
console.log(chalk2.cyan.bold("Flow Timeline"));
|
|
1517
|
-
console.log(chalk2.gray("\u2500".repeat(50)));
|
|
1518
|
-
console.log(new TimelineBuilder().renderAscii(timeline));
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
if (incident.notes.length > 0) {
|
|
1522
|
-
console.log("");
|
|
1523
|
-
console.log(chalk2.cyan.bold("Notes"));
|
|
1524
|
-
console.log(chalk2.gray("\u2500".repeat(50)));
|
|
1525
|
-
for (const note of incident.notes) {
|
|
1526
|
-
console.log(
|
|
1527
|
-
chalk2.gray(note.timestamp.substring(0, 16)) + " " + (note.author ? chalk2.yellow(note.author + ": ") : "") + note.content
|
|
1528
|
-
);
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
if (incident.relatedIncidents.length > 0) {
|
|
1532
|
-
console.log("");
|
|
1533
|
-
console.log(chalk2.cyan.bold("Related Incidents"));
|
|
1534
|
-
console.log(chalk2.gray("\u2500".repeat(50)));
|
|
1535
|
-
console.log(" " + incident.relatedIncidents.join(", "));
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
async function triageResolveCommand(incidentId, options) {
|
|
1539
|
-
const store = getStorage();
|
|
1540
|
-
const incident = store.getIncident(incidentId);
|
|
1541
|
-
if (!incident) {
|
|
1542
|
-
console.log(chalk2.red(`Incident ${incidentId} not found.`));
|
|
1543
|
-
return;
|
|
1544
|
-
}
|
|
1545
|
-
if (incident.status === "resolved" || incident.status === "wont-fix") {
|
|
1546
|
-
console.log(chalk2.yellow(`Incident ${incidentId} is already ${incident.status}.`));
|
|
1547
|
-
return;
|
|
1548
|
-
}
|
|
1549
|
-
if (options.wontFix) {
|
|
1550
|
-
store.updateIncident(incidentId, {
|
|
1551
|
-
status: "wont-fix",
|
|
1552
|
-
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1553
|
-
resolvedBy: "manual",
|
|
1554
|
-
resolution: {
|
|
1555
|
-
notes: options.notes
|
|
1556
|
-
}
|
|
1557
|
-
});
|
|
1558
|
-
console.log(chalk2.gray(`Incident ${incidentId} marked as won't fix.`));
|
|
1559
|
-
return;
|
|
1560
|
-
}
|
|
1561
|
-
store.recordResolution({
|
|
1562
|
-
incidentId,
|
|
1563
|
-
patternId: options.pattern,
|
|
1564
|
-
commitHash: options.commit,
|
|
1565
|
-
prUrl: options.pr,
|
|
1566
|
-
notes: options.notes
|
|
1567
|
-
});
|
|
1568
|
-
console.log(chalk2.green(`Incident ${incidentId} resolved.`));
|
|
1569
|
-
if (options.pattern) {
|
|
1570
|
-
console.log(chalk2.gray(` Pattern: ${options.pattern}`));
|
|
1571
|
-
}
|
|
1572
|
-
if (options.commit) {
|
|
1573
|
-
console.log(chalk2.gray(` Commit: ${options.commit}`));
|
|
1574
|
-
}
|
|
1575
|
-
if (options.pr) {
|
|
1576
|
-
console.log(chalk2.gray(` PR: ${options.pr}`));
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
async function triageNoteCommand(incidentId, note) {
|
|
1580
|
-
const store = getStorage();
|
|
1581
|
-
const incident = store.getIncident(incidentId);
|
|
1582
|
-
if (!incident) {
|
|
1583
|
-
console.log(chalk2.red(`Incident ${incidentId} not found.`));
|
|
1584
|
-
return;
|
|
1585
|
-
}
|
|
1586
|
-
store.addIncidentNote(incidentId, {
|
|
1587
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1588
|
-
content: note
|
|
1589
|
-
});
|
|
1590
|
-
console.log(chalk2.green(`Note added to ${incidentId}.`));
|
|
1591
|
-
}
|
|
1592
|
-
async function triageLinkCommand(incidentId1, incidentId2) {
|
|
1593
|
-
const store = getStorage();
|
|
1594
|
-
const inc1 = store.getIncident(incidentId1);
|
|
1595
|
-
const inc2 = store.getIncident(incidentId2);
|
|
1596
|
-
if (!inc1) {
|
|
1597
|
-
console.log(chalk2.red(`Incident ${incidentId1} not found.`));
|
|
1598
|
-
return;
|
|
1599
|
-
}
|
|
1600
|
-
if (!inc2) {
|
|
1601
|
-
console.log(chalk2.red(`Incident ${incidentId2} not found.`));
|
|
1602
|
-
return;
|
|
1603
|
-
}
|
|
1604
|
-
store.linkIncidents(incidentId1, incidentId2);
|
|
1605
|
-
console.log(chalk2.green(`Linked ${incidentId1} and ${incidentId2}.`));
|
|
1606
|
-
}
|
|
1607
|
-
async function triagePatternsListCommand(options) {
|
|
1608
|
-
const store = getStorage();
|
|
1609
|
-
const patterns = store.getAllPatterns({
|
|
1610
|
-
source: options.source,
|
|
1611
|
-
minConfidence: options.minConfidence ? parseInt(options.minConfidence, 10) : void 0,
|
|
1612
|
-
includePrivate: true
|
|
1613
|
-
});
|
|
1614
|
-
if (options.json) {
|
|
1615
|
-
console.log(JSON.stringify(patterns, null, 2));
|
|
1616
|
-
return;
|
|
1617
|
-
}
|
|
1618
|
-
console.log(chalk2.cyan.bold("\nFailure Patterns"));
|
|
1619
|
-
console.log(chalk2.gray("\u2500".repeat(70)));
|
|
1620
|
-
if (patterns.length === 0) {
|
|
1621
|
-
console.log(chalk2.gray("No patterns found. Run `paradigm triage patterns seed` to load defaults."));
|
|
1622
|
-
return;
|
|
1623
|
-
}
|
|
1624
|
-
for (const pattern of patterns) {
|
|
1625
|
-
console.log(formatPatternCompact(pattern));
|
|
1626
|
-
console.log("");
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
async function triagePatternsShowCommand(patternId, options) {
|
|
1630
|
-
const store = getStorage();
|
|
1631
|
-
const pattern = store.getPattern(patternId);
|
|
1632
|
-
if (!pattern) {
|
|
1633
|
-
console.log(chalk2.red(`Pattern ${patternId} not found.`));
|
|
1634
|
-
return;
|
|
1635
|
-
}
|
|
1636
|
-
if (options.json) {
|
|
1637
|
-
console.log(JSON.stringify(pattern, null, 2));
|
|
1638
|
-
return;
|
|
1639
|
-
}
|
|
1640
|
-
console.log(formatPattern(pattern));
|
|
1641
|
-
}
|
|
1642
|
-
async function triagePatternsAddCommand(options) {
|
|
1643
|
-
const store = getStorage();
|
|
1644
|
-
if (options.fromIncident) {
|
|
1645
|
-
const incident = store.getIncident(options.fromIncident);
|
|
1646
|
-
if (!incident) {
|
|
1647
|
-
console.log(chalk2.red(`Incident ${options.fromIncident} not found.`));
|
|
1648
|
-
return;
|
|
1649
|
-
}
|
|
1650
|
-
const suggester = new PatternSuggester(store);
|
|
1651
|
-
const suggested = suggester.suggestFromIncident(incident);
|
|
1652
|
-
console.log(chalk2.cyan("\nSuggested Pattern:"));
|
|
1653
|
-
console.log(JSON.stringify(suggested, null, 2));
|
|
1654
|
-
console.log(
|
|
1655
|
-
chalk2.gray(
|
|
1656
|
-
'\nEdit and add with: paradigm triage patterns add --id <id> --name "..." ...'
|
|
1657
|
-
)
|
|
1658
|
-
);
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1661
|
-
const symbols = {};
|
|
1662
|
-
if (options.symbols) {
|
|
1663
|
-
const pairs = options.symbols.split(",");
|
|
1664
|
-
for (const pair of pairs) {
|
|
1665
|
-
const [key, value] = pair.split(":").map((s) => s.trim());
|
|
1666
|
-
if (key && value) {
|
|
1667
|
-
symbols[key] = value;
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
const input = {
|
|
1672
|
-
id: options.id,
|
|
1673
|
-
name: options.name,
|
|
1674
|
-
description: options.description || "",
|
|
1675
|
-
pattern: {
|
|
1676
|
-
symbols,
|
|
1677
|
-
errorContains: options.errorContains?.split(",").map((s) => s.trim()),
|
|
1678
|
-
missingSignals: options.missingSignals?.split(",").map((s) => s.trim())
|
|
1679
|
-
},
|
|
1680
|
-
resolution: {
|
|
1681
|
-
description: "Resolution TBD",
|
|
1682
|
-
strategy: options.strategy || "fix-code",
|
|
1683
|
-
priority: options.priority || "medium",
|
|
1684
|
-
codeHint: options.codeHint
|
|
1685
|
-
},
|
|
1686
|
-
source: "manual",
|
|
1687
|
-
private: false,
|
|
1688
|
-
tags: options.tags?.split(",").map((s) => s.trim()) || []
|
|
1689
|
-
};
|
|
1690
|
-
store.addPattern(input);
|
|
1691
|
-
console.log(chalk2.green(`Pattern ${options.id} created.`));
|
|
1692
|
-
}
|
|
1693
|
-
async function triagePatternsDeleteCommand(patternId) {
|
|
1694
|
-
const store = getStorage();
|
|
1695
|
-
const pattern = store.getPattern(patternId);
|
|
1696
|
-
if (!pattern) {
|
|
1697
|
-
console.log(chalk2.red(`Pattern ${patternId} not found.`));
|
|
1698
|
-
return;
|
|
1699
|
-
}
|
|
1700
|
-
store.deletePattern(patternId);
|
|
1701
|
-
console.log(chalk2.green(`Pattern ${patternId} deleted.`));
|
|
1702
|
-
}
|
|
1703
|
-
async function triagePatternsTestCommand(patternId, options) {
|
|
1704
|
-
const store = getStorage();
|
|
1705
|
-
const matcher = new PatternMatcher(store);
|
|
1706
|
-
const pattern = store.getPattern(patternId);
|
|
1707
|
-
if (!pattern) {
|
|
1708
|
-
console.log(chalk2.red(`Pattern ${patternId} not found.`));
|
|
1709
|
-
return;
|
|
1710
|
-
}
|
|
1711
|
-
const limit = parseInt(options.limit || "100", 10);
|
|
1712
|
-
const result = matcher.testPattern(pattern, limit);
|
|
1713
|
-
if (options.json) {
|
|
1714
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1715
|
-
return;
|
|
1716
|
-
}
|
|
1717
|
-
console.log(chalk2.cyan.bold(`
|
|
1718
|
-
Pattern Test: ${patternId}`));
|
|
1719
|
-
console.log(chalk2.gray("\u2500".repeat(50)));
|
|
1720
|
-
console.log(`Would match: ${chalk2.yellow(result.matchCount)} incidents`);
|
|
1721
|
-
console.log(`Average score: ${chalk2.yellow(result.avgScore + "%")}`);
|
|
1722
|
-
if (result.wouldMatch.length > 0) {
|
|
1723
|
-
console.log("");
|
|
1724
|
-
console.log(chalk2.cyan("Sample matches:"));
|
|
1725
|
-
for (const incident of result.wouldMatch.slice(0, 5)) {
|
|
1726
|
-
console.log(formatIncidentCompact(incident));
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
async function triagePatternsSeedCommand() {
|
|
1731
|
-
const store = getStorage();
|
|
1732
|
-
const spinner = ora("Loading seed patterns...").start();
|
|
1733
|
-
try {
|
|
1734
|
-
const seedData = loadAllSeedPatterns();
|
|
1735
|
-
const result = store.importPatterns(seedData, { overwrite: false });
|
|
1736
|
-
spinner.succeed(
|
|
1737
|
-
`Loaded ${result.imported} patterns (${result.skipped} skipped).`
|
|
1738
|
-
);
|
|
1739
|
-
} catch (error) {
|
|
1740
|
-
spinner.fail(`Failed to load seed patterns: ${error}`);
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
async function triageExportCommand(type, options) {
|
|
1744
|
-
const store = getStorage();
|
|
1745
|
-
let data;
|
|
1746
|
-
let defaultFilename;
|
|
1747
|
-
if (type === "patterns") {
|
|
1748
|
-
data = store.exportPatterns({
|
|
1749
|
-
includePrivate: options.includePrivate
|
|
1750
|
-
});
|
|
1751
|
-
defaultFilename = "sentinel-patterns.json";
|
|
1752
|
-
} else {
|
|
1753
|
-
data = store.exportBackup();
|
|
1754
|
-
defaultFilename = "sentinel-backup.json";
|
|
1755
|
-
}
|
|
1756
|
-
const outputPath = options.output || defaultFilename;
|
|
1757
|
-
fs2.writeFileSync(outputPath, JSON.stringify(data, null, 2));
|
|
1758
|
-
console.log(chalk2.green(`Exported to ${outputPath}`));
|
|
1759
|
-
}
|
|
1760
|
-
async function triageImportCommand(filePath, options) {
|
|
1761
|
-
const store = getStorage();
|
|
1762
|
-
const importer = new PatternImporter();
|
|
1763
|
-
const spinner = ora(`Importing from ${filePath}...`).start();
|
|
1764
|
-
try {
|
|
1765
|
-
const data = importer.loadFromFile(filePath);
|
|
1766
|
-
const result = store.importPatterns(data, {
|
|
1767
|
-
overwrite: options.overwrite
|
|
1768
|
-
});
|
|
1769
|
-
spinner.succeed(
|
|
1770
|
-
`Imported ${result.imported} patterns (${result.skipped} skipped).`
|
|
1771
|
-
);
|
|
1772
|
-
} catch (error) {
|
|
1773
|
-
spinner.fail(`Import failed: ${error}`);
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
async function triageRestoreCommand(filePath) {
|
|
1777
|
-
const store = getStorage();
|
|
1778
|
-
const spinner = ora(`Restoring from ${filePath}...`).start();
|
|
1779
|
-
try {
|
|
1780
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1781
|
-
const data = JSON.parse(content);
|
|
1782
|
-
store.importBackup(data);
|
|
1783
|
-
spinner.succeed("Backup restored.");
|
|
1784
|
-
} catch (error) {
|
|
1785
|
-
spinner.fail(`Restore failed: ${error}`);
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
async function triageStatsCommand(options) {
|
|
1789
|
-
const store = getStorage();
|
|
1790
|
-
const calculator = new StatsCalculator(store);
|
|
1791
|
-
let periodDays = 7;
|
|
1792
|
-
if (options.period) {
|
|
1793
|
-
const match = options.period.match(/^(\d+)d$/);
|
|
1794
|
-
if (match) {
|
|
1795
|
-
periodDays = parseInt(match[1], 10);
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
if (options.symbol) {
|
|
1799
|
-
const health = calculator.getSymbolHealth(options.symbol);
|
|
1800
|
-
if (options.json) {
|
|
1801
|
-
console.log(JSON.stringify(health, null, 2));
|
|
1802
|
-
} else {
|
|
1803
|
-
console.log(chalk2.cyan.bold(`
|
|
1804
|
-
Symbol Health: ${options.symbol}`));
|
|
1805
|
-
console.log(chalk2.gray("\u2500".repeat(50)));
|
|
1806
|
-
console.log(` Incidents: ${health.incidentCount}`);
|
|
1807
|
-
console.log(` Avg Time to Resolve: ${health.avgTimeToResolve}m`);
|
|
1808
|
-
console.log("");
|
|
1809
|
-
console.log(chalk2.cyan(" Top Patterns:"));
|
|
1810
|
-
for (const { patternId, count } of health.topPatterns) {
|
|
1811
|
-
console.log(` ${patternId}: ${count} times`);
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
return;
|
|
1815
|
-
}
|
|
1816
|
-
const stats = calculator.getStats(periodDays);
|
|
1817
|
-
if (options.json) {
|
|
1818
|
-
console.log(JSON.stringify(stats, null, 2));
|
|
1819
|
-
return;
|
|
1820
|
-
}
|
|
1821
|
-
console.log(calculator.generateDashboard(periodDays));
|
|
1822
|
-
}
|
|
1823
|
-
async function triageRecordCommand(options) {
|
|
1824
|
-
const store = getStorage();
|
|
1825
|
-
const matcher = new PatternMatcher(store);
|
|
1826
|
-
const incidentId = store.recordIncident({
|
|
1827
|
-
error: {
|
|
1828
|
-
message: options.error,
|
|
1829
|
-
stack: options.stack
|
|
1830
|
-
},
|
|
1831
|
-
symbols: {
|
|
1832
|
-
feature: options.feature,
|
|
1833
|
-
component: options.component,
|
|
1834
|
-
flow: options.flow,
|
|
1835
|
-
gate: options.gate,
|
|
1836
|
-
signal: options.signal,
|
|
1837
|
-
state: options.state,
|
|
1838
|
-
integration: options.integration
|
|
1839
|
-
},
|
|
1840
|
-
environment: options.env,
|
|
1841
|
-
service: options.service,
|
|
1842
|
-
version: options.version
|
|
1843
|
-
});
|
|
1844
|
-
const incident = store.getIncident(incidentId);
|
|
1845
|
-
const matches = incident ? matcher.match(incident, { maxResults: 3 }) : [];
|
|
1846
|
-
if (options.json) {
|
|
1847
|
-
console.log(JSON.stringify({ incidentId, matches }, null, 2));
|
|
1848
|
-
return;
|
|
1849
|
-
}
|
|
1850
|
-
console.log(chalk2.green(`Recorded incident ${incidentId}`));
|
|
1851
|
-
if (matches.length > 0) {
|
|
1852
|
-
console.log("");
|
|
1853
|
-
console.log(chalk2.cyan("Matched patterns:"));
|
|
1854
|
-
for (const match of matches) {
|
|
1855
|
-
console.log(
|
|
1856
|
-
` ${chalk2.yellow("\u2605")} ${match.pattern.id} (${match.confidence}% confidence)`
|
|
1857
|
-
);
|
|
1858
|
-
console.log(chalk2.gray(` ${match.pattern.resolution.description}`));
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
2234
|
export {
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
2235
|
+
PatternMatcher,
|
|
2236
|
+
loadUniversalPatterns,
|
|
2237
|
+
loadParadigmPatterns,
|
|
2238
|
+
loadAllSeedPatterns,
|
|
2239
|
+
FlowTracker,
|
|
2240
|
+
Sentinel,
|
|
2241
|
+
loadConfig,
|
|
2242
|
+
writeConfig,
|
|
2243
|
+
detectSymbols,
|
|
2244
|
+
generateConfig,
|
|
2245
|
+
IncidentGrouper,
|
|
2246
|
+
TimelineBuilder,
|
|
2247
|
+
StatsCalculator,
|
|
2248
|
+
ContextEnricher,
|
|
2249
|
+
PatternSuggester,
|
|
2250
|
+
PatternImporter
|
|
1879
2251
|
};
|