@a-company/sentinel 0.2.0 → 3.6.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/adapters/express.d.ts +3 -1
- package/dist/adapters/fastify.d.ts +3 -1
- package/dist/adapters/hono.d.ts +3 -1
- package/dist/chunk-NTX74ZPM.js +2624 -0
- package/dist/chunk-VQ3SIN7S.js +422 -0
- package/dist/cli.js +6 -6
- package/dist/{commands-KIMGFR2I.js → commands-E7ACLOE7.js} +2367 -258
- package/dist/{dist-2F7NO4H4.js → dist-AG5JNIZU.js} +27 -2
- package/dist/{dist-BPWLYV4U.js → dist-TYG2XME3.js} +27 -2
- package/dist/index.d.ts +57 -5
- package/dist/index.js +288 -186
- package/dist/mcp.js +1538 -124
- package/dist/sdk-CzFDYYST.d.ts +180 -0
- package/dist/server/index.d.ts +20 -3
- package/dist/server/index.js +805 -9
- package/dist/storage-BKyt7aPJ.d.ts +319 -0
- package/dist/transport-DqamniUy.d.ts +185 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +10 -0
- package/dist/{sdk-B27_vK1g.d.ts → types-BmVoO1iF.d.ts} +196 -259
- package/package.json +15 -1
- package/ui/dist/assets/index-BuK-X6af.js +62 -0
- package/ui/dist/assets/index-BuK-X6af.js.map +1 -0
- package/ui/dist/assets/{index-DPxatSdT.css → index-DCtwmE2_.css} +1 -1
- package/ui/dist/index.html +2 -2
- package/dist/chunk-KPMG4XED.js +0 -1249
- package/ui/dist/assets/index-BNgsn_C8.js +0 -62
- package/ui/dist/assets/index-BNgsn_C8.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
SentinelClient,
|
|
3
|
+
SentinelTransport,
|
|
4
|
+
createSentinelClient,
|
|
5
|
+
createSentinelTransport,
|
|
6
|
+
enableSentinel
|
|
7
|
+
} from "./chunk-VQ3SIN7S.js";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_AUTH_CONFIG,
|
|
10
|
+
DEFAULT_RATE_LIMIT_CONFIG,
|
|
11
|
+
DEFAULT_SERVER_CONFIG,
|
|
12
|
+
SentinelStorage,
|
|
13
|
+
loadConfig,
|
|
14
|
+
loadServerConfig,
|
|
15
|
+
writeConfig
|
|
16
|
+
} from "./chunk-NTX74ZPM.js";
|
|
4
17
|
|
|
5
18
|
// src/matcher.ts
|
|
6
19
|
var DEFAULT_CONFIG = {
|
|
@@ -498,143 +511,9 @@ var Sentinel = class {
|
|
|
498
511
|
}
|
|
499
512
|
};
|
|
500
513
|
|
|
501
|
-
// src/
|
|
514
|
+
// src/detector.ts
|
|
502
515
|
import * as fs2 from "fs";
|
|
503
516
|
import * as path2 from "path";
|
|
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
|
-
|
|
635
|
-
// src/detector.ts
|
|
636
|
-
import * as fs3 from "fs";
|
|
637
|
-
import * as path3 from "path";
|
|
638
517
|
var DIR_PATTERNS = [
|
|
639
518
|
{ dirs: ["services", "src/services"], prefix: "#", type: "components" },
|
|
640
519
|
{ dirs: ["routes", "src/routes", "api", "src/api"], prefix: "#", type: "components" },
|
|
@@ -669,13 +548,13 @@ function detectSymbols(projectDir) {
|
|
|
669
548
|
}
|
|
670
549
|
for (const pattern of DIR_PATTERNS) {
|
|
671
550
|
for (const dir of pattern.dirs) {
|
|
672
|
-
const fullPath =
|
|
673
|
-
if (!
|
|
551
|
+
const fullPath = path2.join(projectDir, dir);
|
|
552
|
+
if (!fs2.existsSync(fullPath)) continue;
|
|
674
553
|
const files = safeReaddir(fullPath);
|
|
675
554
|
for (const file of files) {
|
|
676
|
-
const ext =
|
|
555
|
+
const ext = path2.extname(file);
|
|
677
556
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
678
|
-
const name =
|
|
557
|
+
const name = path2.basename(file, ext);
|
|
679
558
|
if (name === "index" || name.endsWith(".test") || name.endsWith(".spec")) continue;
|
|
680
559
|
const symbol = `${pattern.prefix}${toKebabCase(name)}`;
|
|
681
560
|
if (!result[pattern.type].includes(symbol)) {
|
|
@@ -691,7 +570,7 @@ function generateConfig(projectDir) {
|
|
|
691
570
|
const detected = detectSymbols(projectDir);
|
|
692
571
|
return {
|
|
693
572
|
version: "1.0",
|
|
694
|
-
project:
|
|
573
|
+
project: path2.basename(projectDir),
|
|
695
574
|
symbols: {
|
|
696
575
|
components: detected.components.length > 0 ? detected.components : void 0,
|
|
697
576
|
gates: detected.gates.length > 0 ? detected.gates : void 0,
|
|
@@ -702,8 +581,8 @@ function generateConfig(projectDir) {
|
|
|
702
581
|
};
|
|
703
582
|
}
|
|
704
583
|
function readPurposeFiles(projectDir) {
|
|
705
|
-
const paradigmDir =
|
|
706
|
-
if (!
|
|
584
|
+
const paradigmDir = path2.join(projectDir, ".paradigm");
|
|
585
|
+
if (!fs2.existsSync(paradigmDir)) return null;
|
|
707
586
|
const result = {
|
|
708
587
|
components: [],
|
|
709
588
|
gates: [],
|
|
@@ -714,7 +593,7 @@ function readPurposeFiles(projectDir) {
|
|
|
714
593
|
const purposeFiles = findFiles(projectDir, ".purpose");
|
|
715
594
|
for (const file of purposeFiles) {
|
|
716
595
|
try {
|
|
717
|
-
const content =
|
|
596
|
+
const content = fs2.readFileSync(file, "utf-8");
|
|
718
597
|
extractPurposeSymbols(content, result);
|
|
719
598
|
} catch {
|
|
720
599
|
}
|
|
@@ -767,13 +646,13 @@ function extractPurposeSymbols(content, result) {
|
|
|
767
646
|
function scanRoutes(projectDir, result) {
|
|
768
647
|
const routeDirs = ["routes", "src/routes", "api", "src/api"];
|
|
769
648
|
for (const dir of routeDirs) {
|
|
770
|
-
const fullPath =
|
|
771
|
-
if (!
|
|
649
|
+
const fullPath = path2.join(projectDir, dir);
|
|
650
|
+
if (!fs2.existsSync(fullPath)) continue;
|
|
772
651
|
const files = safeReaddir(fullPath);
|
|
773
652
|
for (const file of files) {
|
|
774
|
-
const ext =
|
|
653
|
+
const ext = path2.extname(file);
|
|
775
654
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
776
|
-
const name =
|
|
655
|
+
const name = path2.basename(file, ext);
|
|
777
656
|
if (name === "index") continue;
|
|
778
657
|
const routePrefix = `/api/${toKebabCase(name)}`;
|
|
779
658
|
const component = `#${toKebabCase(name)}`;
|
|
@@ -786,10 +665,10 @@ function toKebabCase(str) {
|
|
|
786
665
|
}
|
|
787
666
|
function safeReaddir(dir) {
|
|
788
667
|
try {
|
|
789
|
-
return
|
|
790
|
-
const fullPath =
|
|
668
|
+
return fs2.readdirSync(dir).filter((f) => {
|
|
669
|
+
const fullPath = path2.join(dir, f);
|
|
791
670
|
try {
|
|
792
|
-
return
|
|
671
|
+
return fs2.statSync(fullPath).isFile();
|
|
793
672
|
} catch {
|
|
794
673
|
return false;
|
|
795
674
|
}
|
|
@@ -803,12 +682,12 @@ function findFiles(dir, filename, maxDepth = 4, depth = 0) {
|
|
|
803
682
|
const results = [];
|
|
804
683
|
const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".next", ".nuxt"]);
|
|
805
684
|
try {
|
|
806
|
-
const entries =
|
|
685
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
807
686
|
for (const entry of entries) {
|
|
808
687
|
if (entry.isFile() && entry.name === filename) {
|
|
809
|
-
results.push(
|
|
688
|
+
results.push(path2.join(dir, entry.name));
|
|
810
689
|
} else if (entry.isDirectory() && !skipDirs.has(entry.name)) {
|
|
811
|
-
results.push(...findFiles(
|
|
690
|
+
results.push(...findFiles(path2.join(dir, entry.name), filename, maxDepth, depth + 1));
|
|
812
691
|
}
|
|
813
692
|
}
|
|
814
693
|
} catch {
|
|
@@ -817,11 +696,18 @@ function findFiles(dir, filename, maxDepth = 4, depth = 0) {
|
|
|
817
696
|
}
|
|
818
697
|
|
|
819
698
|
// src/grouper.ts
|
|
820
|
-
var
|
|
699
|
+
var DEFAULT_SIMILARITY_THRESHOLD = 0.6;
|
|
700
|
+
var DECAY_HALF_LIFE_DAYS = 14;
|
|
821
701
|
var IncidentGrouper = class {
|
|
822
|
-
constructor(storage) {
|
|
702
|
+
constructor(storage, config) {
|
|
823
703
|
this.storage = storage;
|
|
704
|
+
this.similarityThreshold = config?.similarityThreshold ?? DEFAULT_SIMILARITY_THRESHOLD;
|
|
705
|
+
this.decayHalfLifeDays = config?.decayHalfLifeDays ?? DECAY_HALF_LIFE_DAYS;
|
|
706
|
+
this.useStackFingerprint = config?.useStackFingerprint ?? true;
|
|
824
707
|
}
|
|
708
|
+
similarityThreshold;
|
|
709
|
+
decayHalfLifeDays;
|
|
710
|
+
useStackFingerprint;
|
|
825
711
|
/**
|
|
826
712
|
* Try to find or create a group for an incident
|
|
827
713
|
* Returns the group ID if grouped, null if no suitable group
|
|
@@ -867,7 +753,7 @@ var IncidentGrouper = class {
|
|
|
867
753
|
continue;
|
|
868
754
|
}
|
|
869
755
|
const score = this.calculateSimilarity(incident, candidate);
|
|
870
|
-
if (score >=
|
|
756
|
+
if (score >= this.similarityThreshold) {
|
|
871
757
|
similar.push({ incident: candidate, score });
|
|
872
758
|
}
|
|
873
759
|
}
|
|
@@ -888,7 +774,7 @@ var IncidentGrouper = class {
|
|
|
888
774
|
continue;
|
|
889
775
|
}
|
|
890
776
|
const similar = ungrouped.filter(
|
|
891
|
-
(other) => other.id !== incident.id && !processed.has(other.id) && this.calculateSimilarity(incident, other) >=
|
|
777
|
+
(other) => other.id !== incident.id && !processed.has(other.id) && this.calculateSimilarity(incident, other) >= this.similarityThreshold
|
|
892
778
|
);
|
|
893
779
|
if (similar.length + 1 >= minSize) {
|
|
894
780
|
const members = [incident, ...similar];
|
|
@@ -915,11 +801,17 @@ var IncidentGrouper = class {
|
|
|
915
801
|
}
|
|
916
802
|
/**
|
|
917
803
|
* Calculate similarity between two incidents (0-1)
|
|
804
|
+
* Applies time-decay so older incidents contribute less, and optionally
|
|
805
|
+
* uses stack trace fingerprinting for more accurate grouping.
|
|
918
806
|
*/
|
|
919
807
|
calculateSimilarity(a, b) {
|
|
920
808
|
let score = 0;
|
|
921
809
|
let maxScore = 0;
|
|
922
|
-
const
|
|
810
|
+
const hasStacks = this.useStackFingerprint && a.error.stack && b.error.stack;
|
|
811
|
+
const symbolWeight = hasStacks ? 0.45 : 0.6;
|
|
812
|
+
const errorWeight = hasStacks ? 0.25 : 0.3;
|
|
813
|
+
const envWeight = 0.1;
|
|
814
|
+
const stackWeight = hasStacks ? 0.2 : 0;
|
|
923
815
|
const symbolTypes = [
|
|
924
816
|
"feature",
|
|
925
817
|
"component",
|
|
@@ -939,19 +831,55 @@ var IncidentGrouper = class {
|
|
|
939
831
|
}
|
|
940
832
|
}
|
|
941
833
|
}
|
|
942
|
-
const errorWeight = 0.3;
|
|
943
834
|
const errorSimilarity = this.stringSimilarity(
|
|
944
835
|
a.error.message,
|
|
945
836
|
b.error.message
|
|
946
837
|
);
|
|
947
838
|
score += errorWeight * errorSimilarity;
|
|
948
839
|
maxScore += errorWeight;
|
|
949
|
-
const envWeight = 0.1;
|
|
950
840
|
if (a.environment === b.environment) {
|
|
951
841
|
score += envWeight;
|
|
952
842
|
}
|
|
953
843
|
maxScore += envWeight;
|
|
954
|
-
|
|
844
|
+
if (hasStacks) {
|
|
845
|
+
const aFingerprint = this.fingerprintStack(a.error.stack);
|
|
846
|
+
const bFingerprint = this.fingerprintStack(b.error.stack);
|
|
847
|
+
const stackSimilarity = this.compareFingerprints(aFingerprint, bFingerprint);
|
|
848
|
+
score += stackWeight * stackSimilarity;
|
|
849
|
+
maxScore += stackWeight;
|
|
850
|
+
}
|
|
851
|
+
const rawScore = maxScore > 0 ? score / maxScore : 0;
|
|
852
|
+
const timeDelta = Math.abs(
|
|
853
|
+
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
854
|
+
);
|
|
855
|
+
const daysDelta = timeDelta / (1e3 * 60 * 60 * 24);
|
|
856
|
+
const decayFactor = Math.pow(0.5, daysDelta / this.decayHalfLifeDays);
|
|
857
|
+
return rawScore * decayFactor;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Extract a fingerprint from a stack trace by normalizing frames.
|
|
861
|
+
* Strips line numbers, column numbers, and absolute paths to capture
|
|
862
|
+
* the structural signature of the call stack.
|
|
863
|
+
*/
|
|
864
|
+
fingerprintStack(stack) {
|
|
865
|
+
return stack.split("\n").filter((line) => line.trim().startsWith("at ")).slice(0, 10).map((frame) => {
|
|
866
|
+
return frame.trim().replace(/:\d+:\d+\)?$/, "").replace(/\(.*[/\\]/, "(").replace(/^\s*at\s+/, "");
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Compare two stack fingerprints (0-1 similarity)
|
|
871
|
+
*/
|
|
872
|
+
compareFingerprints(a, b) {
|
|
873
|
+
if (a.length === 0 && b.length === 0) return 1;
|
|
874
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
875
|
+
let matches = 0;
|
|
876
|
+
const maxLen = Math.max(a.length, b.length);
|
|
877
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
878
|
+
if (a[i] === b[i]) {
|
|
879
|
+
matches++;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return matches / maxLen;
|
|
955
883
|
}
|
|
956
884
|
/**
|
|
957
885
|
* Calculate string similarity using Levenshtein distance
|
|
@@ -1491,8 +1419,8 @@ var StatsCalculator = class {
|
|
|
1491
1419
|
};
|
|
1492
1420
|
|
|
1493
1421
|
// src/enricher.ts
|
|
1494
|
-
import * as
|
|
1495
|
-
import * as
|
|
1422
|
+
import * as fs3 from "fs";
|
|
1423
|
+
import * as path3 from "path";
|
|
1496
1424
|
var ContextEnricher = class {
|
|
1497
1425
|
constructor(projectRoot = process.cwd()) {
|
|
1498
1426
|
this.projectRoot = projectRoot;
|
|
@@ -1564,12 +1492,12 @@ var ContextEnricher = class {
|
|
|
1564
1492
|
* Find symbol in premise index
|
|
1565
1493
|
*/
|
|
1566
1494
|
findInSymbolIndex(symbol) {
|
|
1567
|
-
const indexPath =
|
|
1568
|
-
if (!
|
|
1495
|
+
const indexPath = path3.join(this.projectRoot, ".paradigm", "index.json");
|
|
1496
|
+
if (!fs3.existsSync(indexPath)) {
|
|
1569
1497
|
return null;
|
|
1570
1498
|
}
|
|
1571
1499
|
try {
|
|
1572
|
-
const indexContent =
|
|
1500
|
+
const indexContent = fs3.readFileSync(indexPath, "utf-8");
|
|
1573
1501
|
const index = JSON.parse(indexContent);
|
|
1574
1502
|
if (index.symbols && Array.isArray(index.symbols)) {
|
|
1575
1503
|
return index.symbols.find(
|
|
@@ -1587,8 +1515,8 @@ var ContextEnricher = class {
|
|
|
1587
1515
|
findInPurposeFiles(symbol) {
|
|
1588
1516
|
const searchPaths = this.getSearchPathsForSymbol(symbol);
|
|
1589
1517
|
for (const searchPath of searchPaths) {
|
|
1590
|
-
const fullPath =
|
|
1591
|
-
if (!
|
|
1518
|
+
const fullPath = path3.join(this.projectRoot, searchPath);
|
|
1519
|
+
if (!fs3.existsSync(fullPath)) {
|
|
1592
1520
|
continue;
|
|
1593
1521
|
}
|
|
1594
1522
|
const cached = this.purposeCache.get(fullPath);
|
|
@@ -1599,7 +1527,7 @@ var ContextEnricher = class {
|
|
|
1599
1527
|
continue;
|
|
1600
1528
|
}
|
|
1601
1529
|
try {
|
|
1602
|
-
const content =
|
|
1530
|
+
const content = fs3.readFileSync(fullPath, "utf-8");
|
|
1603
1531
|
const purpose = this.parsePurposeFile(content);
|
|
1604
1532
|
this.purposeCache.set(fullPath, purpose);
|
|
1605
1533
|
if (purpose.symbol === symbol) {
|
|
@@ -1629,11 +1557,11 @@ var ContextEnricher = class {
|
|
|
1629
1557
|
const prefix = symbol[0];
|
|
1630
1558
|
const dirs = prefixDirs[prefix] || [];
|
|
1631
1559
|
for (const dir of dirs) {
|
|
1632
|
-
paths.push(
|
|
1633
|
-
paths.push(
|
|
1560
|
+
paths.push(path3.join(dir, cleanSymbol, ".purpose"));
|
|
1561
|
+
paths.push(path3.join(dir, `${cleanSymbol}.purpose`));
|
|
1634
1562
|
}
|
|
1635
|
-
paths.push(
|
|
1636
|
-
paths.push(
|
|
1563
|
+
paths.push(path3.join(".paradigm", "purposes", `${cleanSymbol}.yaml`));
|
|
1564
|
+
paths.push(path3.join(".paradigm", "purposes", `${cleanSymbol}.json`));
|
|
1637
1565
|
return paths;
|
|
1638
1566
|
}
|
|
1639
1567
|
/**
|
|
@@ -1698,7 +1626,7 @@ var PatternSuggester = class {
|
|
|
1698
1626
|
},
|
|
1699
1627
|
resolution: {
|
|
1700
1628
|
description: incident.resolution?.notes || "Resolution approach TBD",
|
|
1701
|
-
strategy:
|
|
1629
|
+
strategy: this.inferStrategy([incident]),
|
|
1702
1630
|
priority: "medium"
|
|
1703
1631
|
},
|
|
1704
1632
|
source: "suggested",
|
|
@@ -1713,6 +1641,7 @@ var PatternSuggester = class {
|
|
|
1713
1641
|
suggestFromGroup(group) {
|
|
1714
1642
|
const baseId = `group-${group.id.toLowerCase().replace(/[^a-z0-9]/g, "-")}`;
|
|
1715
1643
|
const symbols = this.buildSymbolCriteria(group.commonSymbols);
|
|
1644
|
+
const groupIncidents = group.incidents.slice(0, 20).map((id) => this.storage.getIncident(id)).filter((i) => i != null);
|
|
1716
1645
|
const pattern = {
|
|
1717
1646
|
id: baseId,
|
|
1718
1647
|
name: group.name || `Pattern from group ${group.id}`,
|
|
@@ -1723,7 +1652,7 @@ var PatternSuggester = class {
|
|
|
1723
1652
|
},
|
|
1724
1653
|
resolution: {
|
|
1725
1654
|
description: "Resolution approach TBD based on grouped incidents",
|
|
1726
|
-
strategy: "fix-code",
|
|
1655
|
+
strategy: groupIncidents.length > 0 ? this.inferStrategy(groupIncidents) : "fix-code",
|
|
1727
1656
|
priority: this.getPriorityFromCount(group.count)
|
|
1728
1657
|
},
|
|
1729
1658
|
source: "suggested",
|
|
@@ -2022,21 +1951,38 @@ var PatternSuggester = class {
|
|
|
2022
1951
|
return false;
|
|
2023
1952
|
}
|
|
2024
1953
|
/**
|
|
2025
|
-
* Infer resolution strategy from
|
|
1954
|
+
* Infer resolution strategy from incident error patterns and context.
|
|
1955
|
+
* Uses keyword heuristics across all incident messages to pick the
|
|
1956
|
+
* most likely resolution approach.
|
|
2026
1957
|
*/
|
|
2027
1958
|
inferStrategy(incidents) {
|
|
2028
1959
|
const messages = incidents.map((i) => i.error.message.toLowerCase());
|
|
2029
|
-
|
|
1960
|
+
const hasKeyword = (keywords) => messages.some((m) => keywords.some((k) => m.includes(k)));
|
|
1961
|
+
if (hasKeyword(["revert", "rollback", "regression", "broke after deploy", "since deploy"])) {
|
|
1962
|
+
return "rollback";
|
|
1963
|
+
}
|
|
1964
|
+
if (hasKeyword(["config", "environment variable", "env var", "missing key", "secret", "credential"])) {
|
|
1965
|
+
return "config-change";
|
|
1966
|
+
}
|
|
1967
|
+
if (hasKeyword(["out of memory", "oom", "heap", "memory limit", "capacity", "too many connections", "pool exhausted"])) {
|
|
1968
|
+
return "scale-up";
|
|
1969
|
+
}
|
|
1970
|
+
if (hasKeyword(["timeout", "network", "econnrefused", "econnreset", "dns", "socket hang up"])) {
|
|
2030
1971
|
return "retry";
|
|
2031
1972
|
}
|
|
2032
|
-
if (
|
|
2033
|
-
|
|
2034
|
-
|
|
1973
|
+
if (hasKeyword(["unavailable", "service down", "circuit breaker", "fallback", "503", "502"])) {
|
|
1974
|
+
return "fallback";
|
|
1975
|
+
}
|
|
1976
|
+
if (hasKeyword(["validation", "invalid", "required", "constraint", "duplicate", "not found", "404"])) {
|
|
2035
1977
|
return "fix-data";
|
|
2036
1978
|
}
|
|
2037
|
-
if (
|
|
1979
|
+
if (hasKeyword(["permission", "forbidden", "403", "401", "unauthorized", "access denied"])) {
|
|
2038
1980
|
return "escalate";
|
|
2039
1981
|
}
|
|
1982
|
+
const uniqueTypes = new Set(incidents.map((i) => i.error.type).filter(Boolean));
|
|
1983
|
+
if (uniqueTypes.size > 2) {
|
|
1984
|
+
return "investigate";
|
|
1985
|
+
}
|
|
2040
1986
|
return "fix-code";
|
|
2041
1987
|
}
|
|
2042
1988
|
/**
|
|
@@ -2051,7 +1997,7 @@ var PatternSuggester = class {
|
|
|
2051
1997
|
};
|
|
2052
1998
|
|
|
2053
1999
|
// src/importer.ts
|
|
2054
|
-
import * as
|
|
2000
|
+
import * as fs4 from "fs";
|
|
2055
2001
|
var PatternImporter = class {
|
|
2056
2002
|
/**
|
|
2057
2003
|
* Validate a pattern export file
|
|
@@ -2146,10 +2092,10 @@ var PatternImporter = class {
|
|
|
2146
2092
|
* Load patterns from a JSON file
|
|
2147
2093
|
*/
|
|
2148
2094
|
loadFromFile(filePath) {
|
|
2149
|
-
if (!
|
|
2095
|
+
if (!fs4.existsSync(filePath)) {
|
|
2150
2096
|
throw new Error(`File not found: ${filePath}`);
|
|
2151
2097
|
}
|
|
2152
|
-
const content =
|
|
2098
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
2153
2099
|
const data = JSON.parse(content);
|
|
2154
2100
|
const validation = this.validate(data);
|
|
2155
2101
|
if (!validation.valid) {
|
|
@@ -2249,22 +2195,178 @@ var PatternImporter = class {
|
|
|
2249
2195
|
};
|
|
2250
2196
|
}
|
|
2251
2197
|
};
|
|
2198
|
+
|
|
2199
|
+
// src/schema/builtin-paradigm.ts
|
|
2200
|
+
var PARADIGM_SCHEMA = {
|
|
2201
|
+
id: "paradigm-logger",
|
|
2202
|
+
version: "1.0.0",
|
|
2203
|
+
name: "Paradigm Logger",
|
|
2204
|
+
description: "Structured logs from @a-company/paradigm-logger with symbolic context",
|
|
2205
|
+
scope: {
|
|
2206
|
+
field: "correlationId",
|
|
2207
|
+
type: "string",
|
|
2208
|
+
label: "Correlation",
|
|
2209
|
+
ordering: "independent",
|
|
2210
|
+
sessionField: "sessionId"
|
|
2211
|
+
},
|
|
2212
|
+
eventTypes: [
|
|
2213
|
+
{
|
|
2214
|
+
type: "log:debug",
|
|
2215
|
+
category: "logs",
|
|
2216
|
+
label: "Debug Log",
|
|
2217
|
+
severity: "debug",
|
|
2218
|
+
frequency: "high",
|
|
2219
|
+
fields: [
|
|
2220
|
+
{ name: "symbol", type: "string", indexed: true, display: true },
|
|
2221
|
+
{ name: "symbolType", type: "string", indexed: true, display: true },
|
|
2222
|
+
{ name: "message", type: "string", display: true },
|
|
2223
|
+
{ name: "service", type: "string", indexed: true, display: true },
|
|
2224
|
+
{ name: "durationMs", type: "number", display: true }
|
|
2225
|
+
]
|
|
2226
|
+
},
|
|
2227
|
+
{
|
|
2228
|
+
type: "log:info",
|
|
2229
|
+
category: "logs",
|
|
2230
|
+
label: "Info Log",
|
|
2231
|
+
severity: "info",
|
|
2232
|
+
frequency: "high",
|
|
2233
|
+
fields: [
|
|
2234
|
+
{ name: "symbol", type: "string", indexed: true, display: true },
|
|
2235
|
+
{ name: "symbolType", type: "string", indexed: true, display: true },
|
|
2236
|
+
{ name: "message", type: "string", display: true },
|
|
2237
|
+
{ name: "service", type: "string", indexed: true, display: true },
|
|
2238
|
+
{ name: "durationMs", type: "number", display: true }
|
|
2239
|
+
]
|
|
2240
|
+
},
|
|
2241
|
+
{
|
|
2242
|
+
type: "log:warn",
|
|
2243
|
+
category: "logs",
|
|
2244
|
+
label: "Warning Log",
|
|
2245
|
+
severity: "warn",
|
|
2246
|
+
frequency: "medium",
|
|
2247
|
+
fields: [
|
|
2248
|
+
{ name: "symbol", type: "string", indexed: true, display: true },
|
|
2249
|
+
{ name: "symbolType", type: "string", indexed: true, display: true },
|
|
2250
|
+
{ name: "message", type: "string", display: true },
|
|
2251
|
+
{ name: "service", type: "string", indexed: true, display: true }
|
|
2252
|
+
]
|
|
2253
|
+
},
|
|
2254
|
+
{
|
|
2255
|
+
type: "log:error",
|
|
2256
|
+
category: "logs",
|
|
2257
|
+
label: "Error Log",
|
|
2258
|
+
severity: "error",
|
|
2259
|
+
frequency: "low",
|
|
2260
|
+
fields: [
|
|
2261
|
+
{ name: "symbol", type: "string", indexed: true, display: true },
|
|
2262
|
+
{ name: "symbolType", type: "string", indexed: true, display: true },
|
|
2263
|
+
{ name: "message", type: "string", display: true },
|
|
2264
|
+
{ name: "service", type: "string", indexed: true, display: true }
|
|
2265
|
+
]
|
|
2266
|
+
},
|
|
2267
|
+
{
|
|
2268
|
+
type: "metric:counter",
|
|
2269
|
+
category: "metrics",
|
|
2270
|
+
label: "Counter Metric",
|
|
2271
|
+
severity: "info",
|
|
2272
|
+
frequency: "high",
|
|
2273
|
+
fields: [
|
|
2274
|
+
{ name: "name", type: "string", indexed: true, display: true },
|
|
2275
|
+
{ name: "value", type: "number", display: true },
|
|
2276
|
+
{ name: "tags", type: "object" }
|
|
2277
|
+
]
|
|
2278
|
+
},
|
|
2279
|
+
{
|
|
2280
|
+
type: "metric:gauge",
|
|
2281
|
+
category: "metrics",
|
|
2282
|
+
label: "Gauge Metric",
|
|
2283
|
+
severity: "info",
|
|
2284
|
+
frequency: "medium",
|
|
2285
|
+
fields: [
|
|
2286
|
+
{ name: "name", type: "string", indexed: true, display: true },
|
|
2287
|
+
{ name: "value", type: "number", display: true },
|
|
2288
|
+
{ name: "tags", type: "object" }
|
|
2289
|
+
]
|
|
2290
|
+
},
|
|
2291
|
+
{
|
|
2292
|
+
type: "metric:histogram",
|
|
2293
|
+
category: "metrics",
|
|
2294
|
+
label: "Histogram Metric",
|
|
2295
|
+
severity: "info",
|
|
2296
|
+
frequency: "medium",
|
|
2297
|
+
fields: [
|
|
2298
|
+
{ name: "name", type: "string", indexed: true, display: true },
|
|
2299
|
+
{ name: "value", type: "number", display: true },
|
|
2300
|
+
{ name: "tags", type: "object" }
|
|
2301
|
+
]
|
|
2302
|
+
},
|
|
2303
|
+
{
|
|
2304
|
+
type: "trace:span",
|
|
2305
|
+
category: "traces",
|
|
2306
|
+
label: "Trace Span",
|
|
2307
|
+
severity: "info",
|
|
2308
|
+
frequency: "medium",
|
|
2309
|
+
fields: [
|
|
2310
|
+
{ name: "traceId", type: "string", indexed: true, display: true },
|
|
2311
|
+
{ name: "spanId", type: "string", indexed: true },
|
|
2312
|
+
{ name: "operation", type: "string", display: true },
|
|
2313
|
+
{ name: "durationMs", type: "number", display: true },
|
|
2314
|
+
{ name: "status", type: "string", display: true }
|
|
2315
|
+
]
|
|
2316
|
+
},
|
|
2317
|
+
{
|
|
2318
|
+
type: "incident:recorded",
|
|
2319
|
+
category: "incidents",
|
|
2320
|
+
label: "Incident Recorded",
|
|
2321
|
+
severity: "error",
|
|
2322
|
+
frequency: "low",
|
|
2323
|
+
fields: [
|
|
2324
|
+
{ name: "incidentId", type: "string", indexed: true, display: true },
|
|
2325
|
+
{ name: "errorMessage", type: "string", display: true },
|
|
2326
|
+
{ name: "symbols", type: "object" },
|
|
2327
|
+
{ name: "environment", type: "string", display: true }
|
|
2328
|
+
]
|
|
2329
|
+
}
|
|
2330
|
+
],
|
|
2331
|
+
visualization: {
|
|
2332
|
+
defaultView: "table",
|
|
2333
|
+
categoryColors: {
|
|
2334
|
+
logs: "#3b82f6",
|
|
2335
|
+
metrics: "#22c55e",
|
|
2336
|
+
traces: "#a855f7",
|
|
2337
|
+
incidents: "#ef4444"
|
|
2338
|
+
},
|
|
2339
|
+
summaryFields: ["symbol", "message", "service"],
|
|
2340
|
+
defaultExcluded: ["log:debug"]
|
|
2341
|
+
},
|
|
2342
|
+
tags: ["builtin", "paradigm"]
|
|
2343
|
+
};
|
|
2252
2344
|
export {
|
|
2253
2345
|
ContextEnricher,
|
|
2346
|
+
DEFAULT_AUTH_CONFIG,
|
|
2347
|
+
DEFAULT_RATE_LIMIT_CONFIG,
|
|
2348
|
+
DEFAULT_SERVER_CONFIG,
|
|
2254
2349
|
FlowTracker,
|
|
2255
2350
|
IncidentGrouper,
|
|
2351
|
+
PARADIGM_SCHEMA,
|
|
2256
2352
|
PatternImporter,
|
|
2257
2353
|
PatternMatcher,
|
|
2258
2354
|
PatternSuggester,
|
|
2259
2355
|
Sentinel,
|
|
2356
|
+
SentinelClient,
|
|
2260
2357
|
SentinelStorage,
|
|
2358
|
+
SentinelTransport,
|
|
2261
2359
|
StatsCalculator,
|
|
2262
2360
|
TimelineBuilder,
|
|
2361
|
+
createSentinelClient,
|
|
2362
|
+
createSentinelTransport,
|
|
2263
2363
|
detectSymbols,
|
|
2364
|
+
enableSentinel,
|
|
2264
2365
|
generateConfig,
|
|
2265
2366
|
loadAllSeedPatterns,
|
|
2266
2367
|
loadConfig,
|
|
2267
2368
|
loadParadigmPatterns,
|
|
2369
|
+
loadServerConfig,
|
|
2268
2370
|
loadUniversalPatterns,
|
|
2269
2371
|
writeConfig
|
|
2270
2372
|
};
|