prismalens 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +29 -13
  2. package/dist/bin/prismalens.js +39 -50
  3. package/dist/chunk-2Z7QZSIS.js +20 -0
  4. package/dist/chunk-5BT3XQOG.js +2990 -0
  5. package/dist/chunk-6N4PHBII.js +30 -0
  6. package/dist/chunk-CTKCK7QT.js +387 -0
  7. package/dist/chunk-CVSS5JPC.js +130 -0
  8. package/dist/chunk-HPE5CTO4.js +22 -0
  9. package/dist/chunk-PTBCAI34.js +34 -0
  10. package/dist/chunk-PVQVFRIV.js +452 -0
  11. package/dist/chunk-QDK5PAI3.js +155 -0
  12. package/dist/chunk-SSEIMBQ2.js +204 -0
  13. package/dist/chunk-TGP6QTFO.js +156 -0
  14. package/dist/chunk-V6M6ISKI.js +161 -0
  15. package/dist/chunk-XH6DIKTA.js +83 -0
  16. package/dist/src/cli/auth.js +112 -101
  17. package/dist/src/cli/doctor.js +184 -187
  18. package/dist/src/cli/flags.js +6 -29
  19. package/dist/src/cli/grouping.js +16 -227
  20. package/dist/src/cli/init.js +58 -52
  21. package/dist/src/cli/investigate.js +23 -465
  22. package/dist/src/cli/listen.js +550 -286
  23. package/dist/src/cli/report.js +70 -67
  24. package/dist/src/cli/serve.js +260 -30
  25. package/dist/src/cli/status.js +87 -86
  26. package/package.json +16 -7
  27. package/dist/bin/prismalens.d.ts +0 -3
  28. package/dist/bin/prismalens.d.ts.map +0 -1
  29. package/dist/bin/prismalens.js.map +0 -1
  30. package/dist/src/cli/auth.d.ts +0 -3
  31. package/dist/src/cli/auth.d.ts.map +0 -1
  32. package/dist/src/cli/auth.js.map +0 -1
  33. package/dist/src/cli/doctor.d.ts +0 -18
  34. package/dist/src/cli/doctor.d.ts.map +0 -1
  35. package/dist/src/cli/doctor.js.map +0 -1
  36. package/dist/src/cli/flags.d.ts +0 -2
  37. package/dist/src/cli/flags.d.ts.map +0 -1
  38. package/dist/src/cli/flags.js.map +0 -1
  39. package/dist/src/cli/grouping.d.ts +0 -18
  40. package/dist/src/cli/grouping.d.ts.map +0 -1
  41. package/dist/src/cli/grouping.js.map +0 -1
  42. package/dist/src/cli/init.d.ts +0 -3
  43. package/dist/src/cli/init.d.ts.map +0 -1
  44. package/dist/src/cli/init.js.map +0 -1
  45. package/dist/src/cli/investigate.d.ts +0 -115
  46. package/dist/src/cli/investigate.d.ts.map +0 -1
  47. package/dist/src/cli/investigate.js.map +0 -1
  48. package/dist/src/cli/listen.d.ts +0 -56
  49. package/dist/src/cli/listen.d.ts.map +0 -1
  50. package/dist/src/cli/listen.js.map +0 -1
  51. package/dist/src/cli/report.d.ts +0 -24
  52. package/dist/src/cli/report.d.ts.map +0 -1
  53. package/dist/src/cli/report.js.map +0 -1
  54. package/dist/src/cli/serve.d.ts +0 -3
  55. package/dist/src/cli/serve.d.ts.map +0 -1
  56. package/dist/src/cli/serve.js.map +0 -1
  57. package/dist/src/cli/status.d.ts +0 -19
  58. package/dist/src/cli/status.d.ts.map +0 -1
  59. package/dist/src/cli/status.js.map +0 -1
  60. package/dist/src/config/loader.d.ts +0 -22
  61. package/dist/src/config/loader.d.ts.map +0 -1
  62. package/dist/src/config/loader.js +0 -193
  63. package/dist/src/config/loader.js.map +0 -1
  64. package/dist/src/config/schema.d.ts +0 -463
  65. package/dist/src/config/schema.d.ts.map +0 -1
  66. package/dist/src/config/schema.js +0 -217
  67. package/dist/src/config/schema.js.map +0 -1
  68. package/dist/src/core/caps.d.ts +0 -36
  69. package/dist/src/core/caps.d.ts.map +0 -1
  70. package/dist/src/core/caps.js +0 -36
  71. package/dist/src/core/caps.js.map +0 -1
  72. package/dist/src/core/db.d.ts +0 -3
  73. package/dist/src/core/db.d.ts.map +0 -1
  74. package/dist/src/core/db.js +0 -105
  75. package/dist/src/core/db.js.map +0 -1
  76. package/dist/src/core/detect-repo.d.ts +0 -4
  77. package/dist/src/core/detect-repo.d.ts.map +0 -1
  78. package/dist/src/core/detect-repo.js +0 -28
  79. package/dist/src/core/detect-repo.js.map +0 -1
  80. package/dist/src/core/file-session-store.d.ts +0 -10
  81. package/dist/src/core/file-session-store.d.ts.map +0 -1
  82. package/dist/src/core/file-session-store.js +0 -34
  83. package/dist/src/core/file-session-store.js.map +0 -1
  84. package/dist/src/core/parse-stdin.d.ts +0 -2
  85. package/dist/src/core/parse-stdin.d.ts.map +0 -1
  86. package/dist/src/core/parse-stdin.js +0 -21
  87. package/dist/src/core/parse-stdin.js.map +0 -1
  88. package/dist/src/core/run-investigation.d.ts +0 -54
  89. package/dist/src/core/run-investigation.d.ts.map +0 -1
  90. package/dist/src/core/run-investigation.js +0 -200
  91. package/dist/src/core/run-investigation.js.map +0 -1
  92. package/dist/src/core/session.d.ts +0 -63
  93. package/dist/src/core/session.d.ts.map +0 -1
  94. package/dist/src/core/session.js +0 -45
  95. package/dist/src/core/session.js.map +0 -1
  96. package/dist/src/core/slack-notify.d.ts +0 -9
  97. package/dist/src/core/slack-notify.d.ts.map +0 -1
  98. package/dist/src/core/slack-notify.js +0 -57
  99. package/dist/src/core/slack-notify.js.map +0 -1
  100. package/dist/src/core/sqlite-session-store.d.ts +0 -39
  101. package/dist/src/core/sqlite-session-store.d.ts.map +0 -1
  102. package/dist/src/core/sqlite-session-store.js +0 -239
  103. package/dist/src/core/sqlite-session-store.js.map +0 -1
  104. package/dist/src/http/server.d.ts +0 -31
  105. package/dist/src/http/server.d.ts.map +0 -1
  106. package/dist/src/http/server.js +0 -164
  107. package/dist/src/http/server.js.map +0 -1
  108. package/dist/src/jsonrpc/server.d.ts +0 -15
  109. package/dist/src/jsonrpc/server.d.ts.map +0 -1
  110. package/dist/src/jsonrpc/server.js +0 -244
  111. package/dist/src/jsonrpc/server.js.map +0 -1
  112. package/dist/src/jsonrpc/types.d.ts +0 -12
  113. package/dist/src/jsonrpc/types.d.ts.map +0 -1
  114. package/dist/src/jsonrpc/types.js +0 -4
  115. package/dist/src/jsonrpc/types.js.map +0 -1
  116. package/dist/src/version.d.ts +0 -8
  117. package/dist/src/version.d.ts.map +0 -1
  118. package/dist/src/version.js +0 -27
  119. package/dist/src/version.js.map +0 -1
@@ -1,227 +1,16 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright 2026 Sumit Patel
3
- import { createHash, randomUUID } from "node:crypto";
4
- import { pickServiceLabel } from "../core/run-investigation.js";
5
- export function deriveGroupKey(alert, payload) {
6
- if (typeof payload.groupKey === "string" && payload.groupKey) {
7
- return payload.groupKey;
8
- }
9
- if (payload.groupLabels && typeof payload.groupLabels === "object") {
10
- const labels = payload.groupLabels;
11
- const keys = Object.keys(labels);
12
- if (keys.length > 0) {
13
- const sorted = keys
14
- .sort()
15
- .map((k) => `${k}=${labels[k]}`)
16
- .join(",");
17
- return createHash("sha256").update(sorted).digest("hex");
18
- }
19
- }
20
- const serviceLabel = pickServiceLabel(alert);
21
- const alertname = alert.labels?.alertname || "";
22
- if (serviceLabel) {
23
- return `${alertname}\0${serviceLabel}`;
24
- }
25
- const alertLabels = alert.labels;
26
- if (alertLabels && Object.keys(alertLabels).length > 0) {
27
- const sorted = Object.keys(alertLabels)
28
- .sort()
29
- .map((k) => `${k}=${alertLabels[k]}`)
30
- .join(",");
31
- return createHash("sha256").update(sorted).digest("hex");
32
- }
33
- return "default";
34
- }
35
- export function deriveDedupeKey(alert) {
36
- if (typeof alert.fingerprint === "string" && alert.fingerprint) {
37
- return alert.fingerprint;
38
- }
39
- const alertLabels = alert.labels;
40
- const alertname = alertLabels?.alertname || "";
41
- const sorted = alertLabels
42
- ? Object.keys(alertLabels)
43
- .sort()
44
- .map((k) => `${k}=${alertLabels[k]}`)
45
- .join(",")
46
- : "";
47
- const hash = createHash("sha256").update(sorted).digest("hex");
48
- const startsAt = alert.startsAt || "";
49
- return `${alertname}${hash}${startsAt}`;
50
- }
51
- export function createGroupingLayer(options) {
52
- let shuttingDown = false;
53
- const windows = new Map();
54
- const running = new Map();
55
- const activeAlerts = new Map();
56
- return {
57
- isShuttingDown() {
58
- return shuttingDown;
59
- },
60
- newGroupCount(firing, payload) {
61
- const keys = new Set();
62
- for (const alert of firing) {
63
- const key = deriveGroupKey(alert, payload);
64
- if (!windows.has(key) && !running.has(key)) {
65
- keys.add(key);
66
- }
67
- }
68
- return keys.size;
69
- },
70
- pendingGroups() {
71
- return windows.size + running.size;
72
- },
73
- admit(firing, payload) {
74
- if (shuttingDown) {
75
- options.log("Dropped alert: grouping layer is shutting down");
76
- return;
77
- }
78
- for (const alert of firing) {
79
- const groupKey = deriveGroupKey(alert, payload);
80
- const dedupeKey = deriveDedupeKey(alert);
81
- // 1. Fingerprint-level suppression (Issue #137)
82
- // If this exact fingerprint is already in-flight anywhere, attach the re-page
83
- // to its existing investigation and suppress dispatch.
84
- if (activeAlerts.has(dedupeKey)) {
85
- const active = activeAlerts.get(dedupeKey);
86
- if (active && active.phase === "running" && active.runId) {
87
- const state = running.get(active.groupKey);
88
- /* v8 ignore next */
89
- if (state) {
90
- // Note the re-page on the existing run
91
- state.writeQueue = state.writeQueue
92
- .then(() => options.sessions.appendGroupAlert(state.runId, alert))
93
- /* v8 ignore start */
94
- .catch((err) => {
95
- options.log(`Failed to attach re-page to group ${active.groupKey} (run ${state.runId}): ${err}`);
96
- });
97
- /* v8 ignore stop */
98
- }
99
- else {
100
- options.log(`Suppression lookup hit but run vanished for dedupeKey ${dedupeKey}`);
101
- }
102
- }
103
- // If phase === "window", it's buffered; no action needed.
104
- continue;
105
- }
106
- // 2. Group-level attachment (AC2)
107
- // If the group is already running, attach this NEW alert (different dedupeKey) to it.
108
- if (running.has(groupKey)) {
109
- const state = running.get(groupKey);
110
- /* v8 ignore next */
111
- if (state) {
112
- // Note: we don't need seenKeys here because activeAlerts handles exact dupes above.
113
- activeAlerts.set(dedupeKey, {
114
- phase: "running",
115
- groupKey,
116
- runId: state.runId,
117
- });
118
- state.dedupeKeys.add(dedupeKey);
119
- state.writeQueue = state.writeQueue
120
- .then(() => options.sessions.appendGroupAlert(state.runId, alert))
121
- /* v8 ignore start */
122
- .catch((err) => {
123
- options.log(`Failed to attach late alert to group ${groupKey} (run ${state.runId}): ${err}`);
124
- });
125
- /* v8 ignore stop */
126
- }
127
- continue;
128
- }
129
- if (windows.has(groupKey)) {
130
- const state = windows.get(groupKey);
131
- /* v8 ignore next */
132
- if (state) {
133
- activeAlerts.set(dedupeKey, { phase: "window", groupKey });
134
- state.alerts.push(alert);
135
- }
136
- continue;
137
- }
138
- // IDLE -> WINDOW_OPEN
139
- const state = {
140
- alerts: [alert],
141
- timer: setTimeout(() => {
142
- // timer fires -> RUNNING
143
- const alertsToRun = state.alerts;
144
- windows.delete(groupKey);
145
- const runId = randomUUID();
146
- const dedupeKeys = new Set();
147
- // Move all these dedupe keys to the running phase
148
- for (const a of alertsToRun) {
149
- const dkey = deriveDedupeKey(a);
150
- activeAlerts.set(dkey, {
151
- phase: "running",
152
- groupKey,
153
- runId,
154
- });
155
- dedupeKeys.add(dkey);
156
- }
157
- // synchronously set up the record
158
- const rec = {
159
- groupKey,
160
- formedBy: "window",
161
- alerts: alertsToRun,
162
- lateAlerts: [],
163
- };
164
- const writePromise = Promise.resolve()
165
- .then(() => options.sessions.writeGroupRecord(runId, rec))
166
- .catch((err) => {
167
- options.log(`Failed to write group record for group ${groupKey} (run ${runId}): ${err} — investigation will proceed without it`);
168
- });
169
- running.set(groupKey, {
170
- runId,
171
- writeQueue: writePromise,
172
- dedupeKeys,
173
- });
174
- writePromise
175
- .then(async () => {
176
- // Shutdown may land between the timer firing and the write
177
- // resolving; don't start a new investigation while closing.
178
- if (shuttingDown) {
179
- running.delete(groupKey);
180
- for (const key of dedupeKeys) {
181
- activeAlerts.delete(key);
182
- }
183
- return;
184
- }
185
- try {
186
- await options.runInvestigation(runId, alertsToRun);
187
- }
188
- catch (err) {
189
- options.log(`Investigation for group ${groupKey} failed: ${err}`);
190
- }
191
- finally {
192
- running.delete(groupKey);
193
- // Clean up global fingerprint registry for this run
194
- for (const key of dedupeKeys) {
195
- activeAlerts.delete(key);
196
- }
197
- }
198
- })
199
- .catch((err) => {
200
- options.log(`Investigation for group ${groupKey} failed: ${err}`);
201
- running.delete(groupKey);
202
- for (const key of dedupeKeys) {
203
- activeAlerts.delete(key);
204
- }
205
- });
206
- }, options.windowMs),
207
- };
208
- activeAlerts.set(dedupeKey, { phase: "window", groupKey });
209
- windows.set(groupKey, state);
210
- }
211
- },
212
- shutdown() {
213
- shuttingDown = true;
214
- let dropped = 0;
215
- for (const state of windows.values()) {
216
- clearTimeout(state.timer);
217
- dropped++;
218
- }
219
- windows.clear();
220
- activeAlerts.clear();
221
- if (dropped > 0) {
222
- options.log(`Shutdown: dropped ${dropped} pending grouping windows`);
223
- }
224
- },
225
- };
226
- }
227
- //# sourceMappingURL=grouping.js.map
1
+ import {
2
+ createGroupingLayer,
3
+ deriveDedupeKey,
4
+ deriveGroupKey
5
+ } from "../../chunk-SSEIMBQ2.js";
6
+ import "../../chunk-QDK5PAI3.js";
7
+ import "../../chunk-CVSS5JPC.js";
8
+ import "../../chunk-5BT3XQOG.js";
9
+ import "../../chunk-6N4PHBII.js";
10
+ import "../../chunk-XH6DIKTA.js";
11
+ import "../../chunk-2Z7QZSIS.js";
12
+ export {
13
+ createGroupingLayer,
14
+ deriveDedupeKey,
15
+ deriveGroupKey
16
+ };
@@ -1,28 +1,27 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright 2026 Sumit Patel
3
- /**
4
- * `prismalens init` — scaffold a prismalens.config.yaml in the cwd.
5
- *
6
- * Behaviour (intentionally non-interactive, unlike the retired pl orchestrator's
7
- * wizard): if a prismalens.config.yaml already exists, print a notice and do
8
- * nothing; otherwise write a commented default. Values are sourced from the Zod
9
- * schema's defaults so the scaffold can't drift from what the engine actually uses.
10
- */
1
+ import {
2
+ AgentConfigSchema,
3
+ PlConfigSchema
4
+ } from "../../chunk-TGP6QTFO.js";
5
+ import {
6
+ assertKnownFlags
7
+ } from "../../chunk-PTBCAI34.js";
8
+ import "../../chunk-5BT3XQOG.js";
9
+ import {
10
+ INVESTIGATION_DEFAULTS
11
+ } from "../../chunk-6N4PHBII.js";
12
+ import "../../chunk-XH6DIKTA.js";
13
+ import "../../chunk-2Z7QZSIS.js";
14
+
15
+ // src/cli/init.ts
11
16
  import { existsSync } from "node:fs";
12
17
  import { writeFile } from "node:fs/promises";
13
18
  import { resolve } from "node:path";
14
- import { INVESTIGATION_DEFAULTS } from "@prismalens/config/investigation";
15
19
  import { defineCommand } from "citty";
16
20
  import consola from "consola";
17
- import { AgentConfigSchema, PlConfigSchema, } from "../config/schema.js";
18
- import { assertKnownFlags } from "./flags.js";
19
- const CONFIG_FILENAME = "prismalens.config.yaml";
20
- /** Render a commented default config, sourcing live values from schema defaults. */
21
+ var CONFIG_FILENAME = "prismalens.config.yaml";
21
22
  function renderDefaultConfig(d) {
22
- const harnessOptions = AgentConfigSchema.shape.default
23
- .removeDefault()
24
- .options.join(" | ");
25
- return `# prismalens.config.yaml — investigation engine configuration (ADR-0008/0010).
23
+ const harnessOptions = AgentConfigSchema.shape.default.removeDefault().options.join(" | ");
24
+ return `# prismalens.config.yaml \u2014 investigation engine configuration (ADR-0008/0010).
26
25
  # Generated by \`prismalens init\`. Unset values fall back to built-in defaults;
27
26
  # \`prismalens doctor\` checks the bits that gate a run.
28
27
 
@@ -34,18 +33,18 @@ function renderDefaultConfig(d) {
34
33
  agent:
35
34
  # Which agent harness to rent: ${harnessOptions}
36
35
  default: ${d.agent.default}
37
- # BARE model id, e.g. "gpt-oss:120b" — the harness applies its own provider prefix.
36
+ # BARE model id, e.g. "gpt-oss:120b" \u2014 the harness applies its own provider prefix.
38
37
  # Omit to let the chosen harness pick its own default.
39
38
  # model: gpt-oss:120b
40
39
 
41
40
  # Read-only telemetry + app endpoints the harness may query. Point these at your
42
41
  # stack; defaults assume a local dev setup.
43
42
  telemetry:
44
- prometheusUrl: ${INVESTIGATION_DEFAULTS.telemetry.prometheusUrl}
45
- alertmanagerUrl: ${INVESTIGATION_DEFAULTS.telemetry.alertmanagerUrl}
46
- apiUrl: ${INVESTIGATION_DEFAULTS.telemetry.apiUrl}
43
+ prometheus_url: ${INVESTIGATION_DEFAULTS.telemetry.prometheusUrl}
44
+ alertmanager_url: ${INVESTIGATION_DEFAULTS.telemetry.alertmanagerUrl}
45
+ api_url: ${INVESTIGATION_DEFAULTS.telemetry.apiUrl}
47
46
 
48
- # Read-only log-query system the harness may curl (Loki, Elasticsearch, …).
47
+ # Read-only log-query system the harness may curl (Loki, Elasticsearch, \u2026).
49
48
  # logs:
50
49
  # kind: loki
51
50
  # url: http://localhost:3100
@@ -61,9 +60,9 @@ telemetry:
61
60
 
62
61
  # Where runs, events, and reports are stored.
63
62
  workspace:
64
- base_dir: ${d.workspace.base_dir}
63
+ dir: ${d.workspace.dir}
65
64
 
66
- # \`pl listen\` — token-authed local webhook intake (Alertmanager). The token is
65
+ # \`pl listen\` \u2014 token-authed local webhook intake (Alertmanager). The token is
67
66
  # REQUIRED to start listening regardless of bind address; keep it out of the file via env interpolation.
68
67
  # listen:
69
68
  # host: ${d.listen.host}
@@ -72,30 +71,37 @@ workspace:
72
71
  # max_pending: ${d.listen.max_pending}
73
72
  `;
74
73
  }
75
- export default defineCommand({
76
- meta: {
77
- name: "init",
78
- description: `Scaffold a ${CONFIG_FILENAME} in the current directory\n\nExamples:\n $ pl init\n $ pl init --force`,
79
- },
80
- async run({ args, cmd }) {
81
- try {
82
- assertKnownFlags(args, cmd);
83
- const configPath = resolve(process.cwd(), CONFIG_FILENAME);
84
- if (existsSync(configPath)) {
85
- consola.info(`${CONFIG_FILENAME} already exists at ${configPath} — leaving it untouched.`);
86
- return;
87
- }
88
- // PlConfigSchema.parse({}) yields the fully-defaulted config; the scaffold
89
- // renders those exact values so it never drifts from the schema.
90
- const defaults = PlConfigSchema.parse({});
91
- await writeFile(configPath, renderDefaultConfig(defaults), "utf-8");
92
- consola.success(`Created ${configPath}`);
93
- consola.info("Edit it for your stack, then run `prismalens doctor` to verify your environment.");
94
- }
95
- catch (err) {
96
- consola.error(err instanceof Error ? err.message : String(err));
97
- process.exit(1);
98
- }
99
- },
74
+ var init_default = defineCommand({
75
+ meta: {
76
+ name: "init",
77
+ description: `Scaffold a ${CONFIG_FILENAME} in the current directory
78
+
79
+ Examples:
80
+ $ pl init
81
+ $ pl init --force`
82
+ },
83
+ async run({ args, cmd }) {
84
+ try {
85
+ assertKnownFlags(args, cmd);
86
+ const configPath = resolve(process.cwd(), CONFIG_FILENAME);
87
+ if (existsSync(configPath)) {
88
+ consola.info(
89
+ `${CONFIG_FILENAME} already exists at ${configPath} \u2014 leaving it untouched.`
90
+ );
91
+ return;
92
+ }
93
+ const defaults = PlConfigSchema.parse({});
94
+ await writeFile(configPath, renderDefaultConfig(defaults), "utf-8");
95
+ consola.success(`Created ${configPath}`);
96
+ consola.info(
97
+ "Edit it for your stack, then run `prismalens doctor` to verify your environment."
98
+ );
99
+ } catch (err) {
100
+ consola.error(err instanceof Error ? err.message : String(err));
101
+ process.exit(1);
102
+ }
103
+ }
100
104
  });
101
- //# sourceMappingURL=init.js.map
105
+ export {
106
+ init_default as default
107
+ };