@wrongstack/core 0.313.0 → 0.313.1
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/chronicle/index.js +84 -6391
- package/dist/chronicle/project-server.js +84 -4868
- package/dist/chunk-2BUUIMQB.js +225 -0
- package/dist/chunk-2FK4WTFF.js +207 -0
- package/dist/chunk-2FQL4PDC.js +292 -0
- package/dist/chunk-2KKODIQU.js +2256 -0
- package/dist/chunk-2YU4WVBW.js +134 -0
- package/dist/chunk-3E244UDA.js +356 -0
- package/dist/chunk-3XPVB7BA.js +135 -0
- package/dist/chunk-4LJJYBAU.js +388 -0
- package/dist/chunk-4VWO3ZXL.js +342 -0
- package/dist/chunk-4XV6IR37.js +187 -0
- package/dist/chunk-4ZHTKJHM.js +183 -0
- package/dist/chunk-52RTVH4M.js +361 -0
- package/dist/chunk-57GEWK5X.js +26 -0
- package/dist/chunk-5DSMEVA4.js +931 -0
- package/dist/chunk-5RP3QTRL.js +1223 -0
- package/dist/chunk-5S437PRC.js +50 -0
- package/dist/chunk-5XDUGTKN.js +511 -0
- package/dist/chunk-62V2AFVG.js +422 -0
- package/dist/chunk-65RDIOR2.js +2470 -0
- package/dist/chunk-6AKOTGLM.js +597 -0
- package/dist/chunk-7ZHZE5EO.js +28 -0
- package/dist/chunk-A455ATZB.js +140 -0
- package/dist/chunk-AD7ZFZCS.js +46 -0
- package/dist/chunk-AI4LLQ2U.js +856 -0
- package/dist/chunk-ASX6AW36.js +789 -0
- package/dist/chunk-AT4V4XGI.js +3522 -0
- package/dist/chunk-AWNQ6ED6.js +311 -0
- package/dist/chunk-B4AMLZPB.js +9 -0
- package/dist/chunk-BAKVJLEV.js +417 -0
- package/dist/chunk-BJHVULHO.js +55 -0
- package/dist/chunk-CB2HFKTP.js +9 -0
- package/dist/chunk-CE4ZOCOI.js +1640 -0
- package/dist/chunk-CQQL4ZOQ.js +151 -0
- package/dist/chunk-DEIQMBD2.js +65 -0
- package/dist/chunk-E6I5RXVK.js +58 -0
- package/dist/chunk-EFEHDQGY.js +1589 -0
- package/dist/chunk-EODDVJBM.js +82 -0
- package/dist/chunk-EPC3GZD4.js +308 -0
- package/dist/chunk-FAYRX2AD.js +13 -0
- package/dist/chunk-FFSX2QDQ.js +362 -0
- package/dist/chunk-FHAMQS7Q.js +5856 -0
- package/dist/chunk-FIV4IJL7.js +2263 -0
- package/dist/chunk-FNXVT7HF.js +52 -0
- package/dist/chunk-FSWCWSTS.js +395 -0
- package/dist/chunk-FW5LDDMJ.js +16464 -0
- package/dist/chunk-FXQLIX3K.js +211 -0
- package/dist/chunk-G5AAKEEZ.js +228 -0
- package/dist/chunk-G5S3S5ZN.js +38 -0
- package/dist/chunk-GAZEWQEZ.js +231 -0
- package/dist/chunk-GFJOMSHG.js +125 -0
- package/dist/chunk-GFXZTHNI.js +158 -0
- package/dist/chunk-GM54GJBU.js +960 -0
- package/dist/chunk-GPQ3NJPR.js +900 -0
- package/dist/chunk-GTCU6DZ3.js +1 -0
- package/dist/chunk-H5E77KUJ.js +15 -0
- package/dist/chunk-H72H4XDU.js +1023 -0
- package/dist/chunk-HA4ZYA6T.js +178 -0
- package/dist/chunk-I45KAW2N.js +5020 -0
- package/dist/chunk-IQ35HMQL.js +1396 -0
- package/dist/chunk-IYFBFX34.js +86 -0
- package/dist/chunk-JJPMWPB6.js +109 -0
- package/dist/chunk-JTHKOYWU.js +663 -0
- package/dist/chunk-JVGPR3TS.js +365 -0
- package/dist/chunk-JVKB2TBL.js +48 -0
- package/dist/chunk-JX7VBZDK.js +31 -0
- package/dist/chunk-K3G6JBUC.js +5232 -0
- package/dist/chunk-K4YUNKU7.js +589 -0
- package/dist/chunk-KBZGE6LN.js +710 -0
- package/dist/chunk-KDQJCT2J.js +45 -0
- package/dist/chunk-KI4L2DUL.js +560 -0
- package/dist/chunk-KJ2ZTQSL.js +47 -0
- package/dist/chunk-KSJ5UTP7.js +154 -0
- package/dist/chunk-LHOA5DWA.js +28 -0
- package/dist/chunk-LX5QWLBL.js +27 -0
- package/dist/chunk-LZDDLNCZ.js +279 -0
- package/dist/chunk-M2WF6AXG.js +115 -0
- package/dist/chunk-M7BS2K6U.js +1 -0
- package/dist/chunk-NDBIPFNY.js +102 -0
- package/dist/chunk-NEMIWEXC.js +1199 -0
- package/dist/chunk-NFAYHPRH.js +323 -0
- package/dist/chunk-O37BQ65S.js +21 -0
- package/dist/chunk-O74MJHXU.js +187 -0
- package/dist/chunk-OJZEX73F.js +112 -0
- package/dist/chunk-PAAWLLZX.js +74 -0
- package/dist/chunk-PFXFQQRT.js +339 -0
- package/dist/chunk-PFZO3C6H.js +14 -0
- package/dist/chunk-PHH7LD45.js +113 -0
- package/dist/chunk-QRTG4XTI.js +188 -0
- package/dist/chunk-QVOVMXJR.js +1351 -0
- package/dist/chunk-RCQYT2I4.js +757 -0
- package/dist/chunk-RD6DEE36.js +1178 -0
- package/dist/chunk-RJIZEJ42.js +30 -0
- package/dist/chunk-RPOA7IAE.js +150 -0
- package/dist/chunk-SN2DQRL6.js +1172 -0
- package/dist/chunk-SQJVFEBV.js +41 -0
- package/dist/chunk-SQP64RCS.js +5391 -0
- package/dist/chunk-SVT4RNTR.js +49 -0
- package/dist/chunk-SWUFN7XM.js +43 -0
- package/dist/chunk-SXMVYC5H.js +174 -0
- package/dist/chunk-SZ6AYUIZ.js +23 -0
- package/dist/chunk-T3KGZBU5.js +3171 -0
- package/dist/chunk-TBRL2RCC.js +9 -0
- package/dist/chunk-TRT2HRWZ.js +18 -0
- package/dist/chunk-TYW2GI5I.js +1077 -0
- package/dist/chunk-UIWONWVG.js +4959 -0
- package/dist/chunk-VB3H5WWB.js +151 -0
- package/dist/chunk-W4A6QA3D.js +258 -0
- package/dist/chunk-WBPVKEEE.js +227 -0
- package/dist/chunk-WEBMSQP2.js +123 -0
- package/dist/chunk-WOEZV3Q4.js +333 -0
- package/dist/chunk-WQQON3SV.js +473 -0
- package/dist/chunk-WQTOVMGV.js +119 -0
- package/dist/chunk-WZO23AWO.js +2779 -0
- package/dist/chunk-XBDP4QPW.js +7 -0
- package/dist/chunk-XEBHSJLT.js +103 -0
- package/dist/chunk-XQ2ECNK3.js +1 -0
- package/dist/chunk-XQ2XGIBF.js +2086 -0
- package/dist/chunk-Y2GE5TSI.js +355 -0
- package/dist/chunk-YBUJCZ57.js +351 -0
- package/dist/chunk-YNCVSFGF.js +149 -0
- package/dist/chunk-YUPMXOGP.js +282 -0
- package/dist/chunk-YY6APYUM.js +49 -0
- package/dist/chunk-Z2IJUO45.js +691 -0
- package/dist/chunk-ZEHUCJHY.js +129 -0
- package/dist/chunk-ZYU2YOJ6.js +1496 -0
- package/dist/chunk-ZZ4V3WIZ.js +163 -0
- package/dist/coordination/agents/index.js +145 -6260
- package/dist/coordination/index.js +365 -34324
- package/dist/coordination/mailbox-project-server.js +48 -3276
- package/dist/coordination/provider-status-tracker.d.ts +4 -0
- package/dist/core/agent.d.ts +14 -1
- package/dist/core/index.js +267 -12480
- package/dist/core/model-ref.js +5 -23
- package/dist/core/provider-runner.d.ts +36 -1
- package/dist/design/index.js +50 -1087
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +1599 -21888
- package/dist/execution/prompt-enhancer.js +16 -535
- package/dist/execution/retry-policy.d.ts +31 -1
- package/dist/extension/index.js +7 -311
- package/dist/goal/index.js +33 -3435
- package/dist/hooks/index.js +14 -1138
- package/dist/hq/index.js +112 -5139
- package/dist/hq/protocol.js +21 -668
- package/dist/index.js +1410 -92656
- package/dist/infrastructure/index.js +64 -4811
- package/dist/kernel/events/provider-events.d.ts +15 -0
- package/dist/kernel/index.js +14 -992
- package/dist/models/index.js +40 -1117
- package/dist/notifications/index.js +3 -108
- package/dist/observability/index.js +17 -279
- package/dist/plugin/index.js +584 -14835
- package/dist/prompts/index.js +14 -948
- package/dist/registry/index.js +9 -822
- package/dist/registry-R23UBE7H.js +14 -0
- package/dist/replay/index.js +6 -84
- package/dist/review-finding-store-DKAV3AS6.js +21 -0
- package/dist/review-report-store-OWY3EC3N.js +19 -0
- package/dist/security/index.js +114 -2202
- package/dist/session-catalog/index.js +36 -2103
- package/dist/session-catalog/project-server.js +42 -1435
- package/dist/skills/index.js +35 -1578
- package/dist/storage/index.js +182 -15600
- package/dist/tasking/index.js +10 -676
- package/dist/tools/index.js +52 -9590
- package/dist/types/config/autonomy.d.ts +5 -0
- package/dist/types/config/ui.d.ts +1 -1
- package/dist/types/errors.d.ts +18 -0
- package/dist/types/index.js +121 -1342
- package/dist/types/provider-runner.d.ts +8 -0
- package/dist/types/session-markers.js +8 -144
- package/dist/utils/error.js +3 -4
- package/dist/utils/expect-defined.js +3 -9
- package/dist/utils/heap-watchdog.js +6 -706
- package/dist/utils/index.js +269 -5685
- package/dist/utils/sage-output-block.js +5 -42
- package/dist/wiring/proxy-rewrite.d.ts +18 -0
- package/dist/wiring/proxy-rewrite.js +18 -121
- package/dist/worktree/index.js +6 -887
- package/package.json +4 -4
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DefaultSecretScrubber
|
|
3
|
+
} from "./chunk-3E244UDA.js";
|
|
4
|
+
|
|
5
|
+
// src/security/error-sanitize.ts
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
var scrubber = new DefaultSecretScrubber();
|
|
8
|
+
var ERROR_DETAIL_MAX = 500;
|
|
9
|
+
function messageOf(err) {
|
|
10
|
+
if (err instanceof Error) return err.message;
|
|
11
|
+
if (typeof err === "string") return err;
|
|
12
|
+
try {
|
|
13
|
+
return String(err);
|
|
14
|
+
} catch {
|
|
15
|
+
return "unknown error";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function sanitizeApiError(err) {
|
|
19
|
+
const lower = messageOf(err).toLowerCase();
|
|
20
|
+
if (lower.includes("enoent") || lower.includes("no such file")) {
|
|
21
|
+
return "resource not found";
|
|
22
|
+
}
|
|
23
|
+
if (lower.includes("eacces") || lower.includes("permission")) {
|
|
24
|
+
return "permission denied";
|
|
25
|
+
}
|
|
26
|
+
if (lower.includes("json") && (lower.includes("parse") || lower.includes("unexpected"))) {
|
|
27
|
+
return "malformed data";
|
|
28
|
+
}
|
|
29
|
+
return "internal error";
|
|
30
|
+
}
|
|
31
|
+
function scrubErrorDetail(err) {
|
|
32
|
+
const out = scrubErrorText(messageOf(err));
|
|
33
|
+
return out.length > ERROR_DETAIL_MAX ? `${out.slice(0, ERROR_DETAIL_MAX - 1)}\u2026` : out;
|
|
34
|
+
}
|
|
35
|
+
function scrubErrorText(text) {
|
|
36
|
+
if (!text) return text;
|
|
37
|
+
let out = scrubber.scrub(text);
|
|
38
|
+
const home = safeHomedir();
|
|
39
|
+
if (home && home.length > 2) {
|
|
40
|
+
out = replaceAllCaseInsensitive(out, home, "~");
|
|
41
|
+
const alt = home.includes("\\") ? home.replace(/\\/g, "/") : home.replace(/\//g, "\\");
|
|
42
|
+
if (alt !== home) out = replaceAllCaseInsensitive(out, alt, "~");
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function safeHomedir() {
|
|
47
|
+
try {
|
|
48
|
+
return homedir();
|
|
49
|
+
} catch {
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function replaceAllCaseInsensitive(haystack, needle, replacement) {
|
|
54
|
+
const lowerHay = haystack.toLowerCase();
|
|
55
|
+
const lowerNeedle = needle.toLowerCase();
|
|
56
|
+
let idx = lowerHay.indexOf(lowerNeedle);
|
|
57
|
+
if (idx === -1) return haystack;
|
|
58
|
+
let out = "";
|
|
59
|
+
let from = 0;
|
|
60
|
+
while (idx !== -1) {
|
|
61
|
+
out += haystack.slice(from, idx) + replacement;
|
|
62
|
+
from = idx + needle.length;
|
|
63
|
+
idx = lowerHay.indexOf(lowerNeedle, from);
|
|
64
|
+
}
|
|
65
|
+
return out + haystack.slice(from);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
ERROR_DETAIL_MAX,
|
|
70
|
+
sanitizeApiError,
|
|
71
|
+
scrubErrorDetail,
|
|
72
|
+
scrubErrorText
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=chunk-PAAWLLZX.js.map
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SECRET_FILE_MODE
|
|
3
|
+
} from "./chunk-H5E77KUJ.js";
|
|
4
|
+
import {
|
|
5
|
+
atomicWrite,
|
|
6
|
+
withFileLock
|
|
7
|
+
} from "./chunk-7ZHZE5EO.js";
|
|
8
|
+
|
|
9
|
+
// src/plugins/review-report-store.ts
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import * as fsp from "node:fs/promises";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
|
|
14
|
+
// src/plugins/review-report-types.ts
|
|
15
|
+
var InvalidReportTransitionError = class extends Error {
|
|
16
|
+
from;
|
|
17
|
+
to;
|
|
18
|
+
constructor(from, to, reason) {
|
|
19
|
+
const msg = reason ?? `Invalid report transition from "${from}" to "${to}"`;
|
|
20
|
+
super(msg);
|
|
21
|
+
this.name = "InvalidReportTransitionError";
|
|
22
|
+
this.from = from;
|
|
23
|
+
this.to = to;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var REPORT_TRANSITION_MATRIX = {
|
|
27
|
+
open: /* @__PURE__ */ new Set(["actioned", "completed", "skipped"]),
|
|
28
|
+
actioned: /* @__PURE__ */ new Set(["completed", "skipped"]),
|
|
29
|
+
completed: /* @__PURE__ */ new Set(["open"]),
|
|
30
|
+
skipped: /* @__PURE__ */ new Set(["open"])
|
|
31
|
+
};
|
|
32
|
+
function validateReportTransition(from, to) {
|
|
33
|
+
if (from === to) return;
|
|
34
|
+
const allowed = REPORT_TRANSITION_MATRIX[from];
|
|
35
|
+
if (!allowed?.has(to)) {
|
|
36
|
+
throw new InvalidReportTransitionError(from, to);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function reportEventTypeFor(to) {
|
|
40
|
+
switch (to) {
|
|
41
|
+
case "actioned":
|
|
42
|
+
return "actioned";
|
|
43
|
+
case "completed":
|
|
44
|
+
return "completed";
|
|
45
|
+
case "skipped":
|
|
46
|
+
return "skipped";
|
|
47
|
+
default:
|
|
48
|
+
return "reopened";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/plugins/review-report-store.ts
|
|
53
|
+
var REPORT_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
54
|
+
var REPORT_DEFAULT_PAGE_SIZE = 25;
|
|
55
|
+
var NL = "\n";
|
|
56
|
+
var REPORT_STORE_FILE = "review-reports.jsonl";
|
|
57
|
+
function resolveReportStorePath(projectDir) {
|
|
58
|
+
return path.join(projectDir, REPORT_STORE_FILE);
|
|
59
|
+
}
|
|
60
|
+
var JsonlReportStore = class {
|
|
61
|
+
filePath;
|
|
62
|
+
constructor(projectDir) {
|
|
63
|
+
this.filePath = resolveReportStorePath(projectDir);
|
|
64
|
+
}
|
|
65
|
+
get storePath() {
|
|
66
|
+
return this.filePath;
|
|
67
|
+
}
|
|
68
|
+
// ── Persist (create or reopen) ───────────────────────────────────
|
|
69
|
+
async persist(input) {
|
|
70
|
+
return withFileLock(this.filePath, async () => {
|
|
71
|
+
const existing = await this._findRecord(input.id);
|
|
72
|
+
if (existing) {
|
|
73
|
+
const updated = {
|
|
74
|
+
...existing,
|
|
75
|
+
counts: input.counts,
|
|
76
|
+
totalFindings: input.totalFindings,
|
|
77
|
+
unparseableCount: input.unparseableCount,
|
|
78
|
+
durationSeconds: input.durationSeconds ?? existing.durationSeconds,
|
|
79
|
+
rawText: input.rawText || existing.rawText,
|
|
80
|
+
files: input.files.length > 0 ? input.files : existing.files,
|
|
81
|
+
...input.evidenceStatus !== void 0 ? { evidenceStatus: input.evidenceStatus } : {},
|
|
82
|
+
...input.evidenceChecks !== void 0 ? { evidenceChecks: input.evidenceChecks } : {}
|
|
83
|
+
};
|
|
84
|
+
await fsp.appendFile(this.filePath, JSON.stringify({ __report: 1, data: updated }) + NL, {
|
|
85
|
+
encoding: "utf8",
|
|
86
|
+
mode: SECRET_FILE_MODE
|
|
87
|
+
});
|
|
88
|
+
return updated;
|
|
89
|
+
}
|
|
90
|
+
const report = {
|
|
91
|
+
id: input.id,
|
|
92
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
93
|
+
sessionId: input.sessionId,
|
|
94
|
+
agentId: input.agentId,
|
|
95
|
+
reviewerModel: input.reviewerModel,
|
|
96
|
+
source: input.source,
|
|
97
|
+
reviewStatus: input.reviewStatus,
|
|
98
|
+
lifecycle: "open",
|
|
99
|
+
files: input.files,
|
|
100
|
+
counts: input.counts,
|
|
101
|
+
totalFindings: input.totalFindings,
|
|
102
|
+
unparseableCount: input.unparseableCount,
|
|
103
|
+
durationSeconds: input.durationSeconds,
|
|
104
|
+
rawText: input.rawText,
|
|
105
|
+
...input.cascadeDepth !== void 0 ? { cascadeDepth: input.cascadeDepth } : {},
|
|
106
|
+
...input.evidenceStatus !== void 0 ? { evidenceStatus: input.evidenceStatus } : {},
|
|
107
|
+
...input.evidenceChecks !== void 0 ? { evidenceChecks: input.evidenceChecks } : {}
|
|
108
|
+
};
|
|
109
|
+
const createdEvent = {
|
|
110
|
+
id: randomUUID(),
|
|
111
|
+
reportId: report.id,
|
|
112
|
+
eventType: "created",
|
|
113
|
+
fromLifecycle: null,
|
|
114
|
+
toLifecycle: "open",
|
|
115
|
+
actorId: "system",
|
|
116
|
+
actorKind: "system",
|
|
117
|
+
timestamp: report.reviewedAt
|
|
118
|
+
};
|
|
119
|
+
const lines = JSON.stringify({ __report: 1, data: report }) + NL + JSON.stringify({ __reportEvent: 1, data: createdEvent }) + NL;
|
|
120
|
+
await fsp.appendFile(this.filePath, lines, { encoding: "utf8", mode: SECRET_FILE_MODE });
|
|
121
|
+
return report;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// ── Lifecycle transitions ────────────────────────────────────────
|
|
125
|
+
async transition(reportId, to, actor, opts) {
|
|
126
|
+
return withFileLock(this.filePath, async () => {
|
|
127
|
+
const all = await this._readAll();
|
|
128
|
+
const entry = all.find((e) => e.report.id === reportId);
|
|
129
|
+
if (!entry) throw new Error(`Review report not found: ${reportId}`);
|
|
130
|
+
const from = this._materialize(entry).lifecycle;
|
|
131
|
+
validateReportTransition(from, to);
|
|
132
|
+
const event = {
|
|
133
|
+
id: randomUUID(),
|
|
134
|
+
reportId,
|
|
135
|
+
eventType: reportEventTypeFor(to),
|
|
136
|
+
fromLifecycle: from,
|
|
137
|
+
toLifecycle: to,
|
|
138
|
+
actorId: actor.id,
|
|
139
|
+
actorKind: actor.kind,
|
|
140
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
141
|
+
reason: opts?.reason
|
|
142
|
+
};
|
|
143
|
+
entry.report.lifecycle = to;
|
|
144
|
+
await fsp.appendFile(this.filePath, JSON.stringify({ __reportEvent: 1, data: event }) + NL, {
|
|
145
|
+
encoding: "utf8",
|
|
146
|
+
mode: SECRET_FILE_MODE
|
|
147
|
+
});
|
|
148
|
+
return { ...entry.report };
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async updateEvidence(reportId, status, checks) {
|
|
152
|
+
return withFileLock(this.filePath, async () => {
|
|
153
|
+
const all = await this._readAll();
|
|
154
|
+
const entry = all.find((candidate) => candidate.report.id === reportId);
|
|
155
|
+
if (!entry) throw new Error(`Review report not found: ${reportId}`);
|
|
156
|
+
const updated = {
|
|
157
|
+
...this._materialize(entry),
|
|
158
|
+
evidenceStatus: status,
|
|
159
|
+
evidenceChecks: checks
|
|
160
|
+
};
|
|
161
|
+
await fsp.appendFile(
|
|
162
|
+
this.filePath,
|
|
163
|
+
`${JSON.stringify({ __report: 1, data: updated })}${NL}`,
|
|
164
|
+
{ encoding: "utf8", mode: SECRET_FILE_MODE }
|
|
165
|
+
);
|
|
166
|
+
return updated;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async addNote(reportId, actor, note) {
|
|
170
|
+
return withFileLock(this.filePath, async () => {
|
|
171
|
+
const all = await this._readAll();
|
|
172
|
+
const entry = all.find((e) => e.report.id === reportId);
|
|
173
|
+
if (!entry) throw new Error(`Review report not found: ${reportId}`);
|
|
174
|
+
const materialized = this._materialize(entry);
|
|
175
|
+
const event = {
|
|
176
|
+
id: randomUUID(),
|
|
177
|
+
reportId,
|
|
178
|
+
eventType: "note_added",
|
|
179
|
+
fromLifecycle: materialized.lifecycle,
|
|
180
|
+
toLifecycle: materialized.lifecycle,
|
|
181
|
+
actorId: actor.id,
|
|
182
|
+
actorKind: actor.kind,
|
|
183
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
184
|
+
reason: note
|
|
185
|
+
};
|
|
186
|
+
await fsp.appendFile(this.filePath, JSON.stringify({ __reportEvent: 1, data: event }) + NL, {
|
|
187
|
+
encoding: "utf8",
|
|
188
|
+
mode: SECRET_FILE_MODE
|
|
189
|
+
});
|
|
190
|
+
return materialized;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// ── Query ────────────────────────────────────────────────────────
|
|
194
|
+
async list(opts) {
|
|
195
|
+
const all = await this._readAll();
|
|
196
|
+
let reports = all.map((e) => this._materialize(e));
|
|
197
|
+
if (opts?.statuses && opts.statuses.length > 0) {
|
|
198
|
+
const set = new Set(opts.statuses);
|
|
199
|
+
reports = reports.filter((r) => set.has(r.lifecycle));
|
|
200
|
+
}
|
|
201
|
+
if (opts?.sources && opts.sources.length > 0) {
|
|
202
|
+
const set = new Set(opts.sources);
|
|
203
|
+
reports = reports.filter((r) => set.has(r.source));
|
|
204
|
+
}
|
|
205
|
+
reports.sort((a, b) => b.reviewedAt.localeCompare(a.reviewedAt));
|
|
206
|
+
const limit = opts?.limit ?? REPORT_DEFAULT_PAGE_SIZE;
|
|
207
|
+
return reports.slice(0, limit);
|
|
208
|
+
}
|
|
209
|
+
async get(id) {
|
|
210
|
+
const all = await this._readAll();
|
|
211
|
+
const entry = all.find((e) => e.report.id === id);
|
|
212
|
+
return entry ? this._materialize(entry) : null;
|
|
213
|
+
}
|
|
214
|
+
async getEvents(reportId) {
|
|
215
|
+
const all = await this._readAllLines();
|
|
216
|
+
const events = [];
|
|
217
|
+
for (const line of all) {
|
|
218
|
+
try {
|
|
219
|
+
const parsed = JSON.parse(line);
|
|
220
|
+
if (this._isEventRecord(parsed) && parsed.data.reportId === reportId) {
|
|
221
|
+
events.push(parsed.data);
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return events.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
227
|
+
}
|
|
228
|
+
// ── Compaction ───────────────────────────────────────────────────
|
|
229
|
+
async compact(opts) {
|
|
230
|
+
return withFileLock(this.filePath, async () => {
|
|
231
|
+
const maxAge = opts?.maxAgeMs ?? REPORT_RETENTION_MS;
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
const all = await this._readAll();
|
|
234
|
+
const kept = [];
|
|
235
|
+
let removed = 0;
|
|
236
|
+
let eventsFolded = 0;
|
|
237
|
+
for (const entry of all) {
|
|
238
|
+
const age = now - new Date(entry.report.reviewedAt).getTime();
|
|
239
|
+
const materialized = this._materialize(entry);
|
|
240
|
+
const isTerminal = materialized.lifecycle === "completed" || materialized.lifecycle === "skipped";
|
|
241
|
+
if (isTerminal && age > maxAge) {
|
|
242
|
+
removed++;
|
|
243
|
+
eventsFolded += entry.events.length;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const oldEvents = entry.events.filter(
|
|
247
|
+
(ev) => now - new Date(ev.timestamp).getTime() > maxAge
|
|
248
|
+
);
|
|
249
|
+
if (oldEvents.length > 1) {
|
|
250
|
+
eventsFolded += oldEvents.length - 1;
|
|
251
|
+
const newestOld = oldEvents.sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
|
|
252
|
+
entry.events = [
|
|
253
|
+
newestOld,
|
|
254
|
+
...entry.events.filter((ev) => now - new Date(ev.timestamp).getTime() <= maxAge)
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
kept.push({ record: { __report: 1, data: entry.report }, events: entry.events });
|
|
258
|
+
}
|
|
259
|
+
const lines = [];
|
|
260
|
+
for (const { record, events } of kept) {
|
|
261
|
+
if (record) lines.push(JSON.stringify(record));
|
|
262
|
+
for (const ev of events) {
|
|
263
|
+
lines.push(JSON.stringify({ __reportEvent: 1, data: ev }));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
lines.push(
|
|
267
|
+
JSON.stringify({
|
|
268
|
+
__reportCompact: 1,
|
|
269
|
+
compactedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
270
|
+
removedReports: removed,
|
|
271
|
+
foldedEvents: eventsFolded
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
await atomicWrite(this.filePath, lines.join(NL) + NL, { mode: 384 });
|
|
275
|
+
return { removed, eventsFolded };
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
279
|
+
async _readAllLines() {
|
|
280
|
+
try {
|
|
281
|
+
const raw = await fsp.readFile(this.filePath, "utf8");
|
|
282
|
+
return raw.split(NL).filter((l) => l.trim().length > 0);
|
|
283
|
+
} catch (err) {
|
|
284
|
+
if (err.code === "ENOENT") return [];
|
|
285
|
+
throw err;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
_isReportRecord(v) {
|
|
289
|
+
return typeof v === "object" && v !== null && v["__report"] === 1;
|
|
290
|
+
}
|
|
291
|
+
_isEventRecord(v) {
|
|
292
|
+
return typeof v === "object" && v !== null && v["__reportEvent"] === 1;
|
|
293
|
+
}
|
|
294
|
+
async _readAll() {
|
|
295
|
+
const lines = await this._readAllLines();
|
|
296
|
+
const reports = /* @__PURE__ */ new Map();
|
|
297
|
+
const eventsMap = /* @__PURE__ */ new Map();
|
|
298
|
+
for (const line of lines) {
|
|
299
|
+
try {
|
|
300
|
+
const parsed = JSON.parse(line);
|
|
301
|
+
if (this._isReportRecord(parsed)) {
|
|
302
|
+
reports.set(parsed.data.id, parsed.data);
|
|
303
|
+
if (!eventsMap.has(parsed.data.id)) eventsMap.set(parsed.data.id, []);
|
|
304
|
+
} else if (this._isEventRecord(parsed)) {
|
|
305
|
+
const list = eventsMap.get(parsed.data.reportId);
|
|
306
|
+
if (list) list.push(parsed.data);
|
|
307
|
+
}
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return Array.from(reports.entries()).map(([id, report]) => ({
|
|
312
|
+
report,
|
|
313
|
+
events: eventsMap.get(id) ?? []
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
async _findRecord(id) {
|
|
317
|
+
const all = await this._readAll();
|
|
318
|
+
const entry = all.find((e) => e.report.id === id);
|
|
319
|
+
return entry ? this._materialize(entry) : null;
|
|
320
|
+
}
|
|
321
|
+
_materialize(entry) {
|
|
322
|
+
if (entry.events.length === 0) return { ...entry.report };
|
|
323
|
+
const sorted = [...entry.events].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
324
|
+
const latest = sorted[sorted.length - 1];
|
|
325
|
+
if (latest.eventType !== "note_added") {
|
|
326
|
+
return { ...entry.report, lifecycle: latest.toLifecycle };
|
|
327
|
+
}
|
|
328
|
+
return { ...entry.report };
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
export {
|
|
333
|
+
REPORT_RETENTION_MS,
|
|
334
|
+
REPORT_DEFAULT_PAGE_SIZE,
|
|
335
|
+
REPORT_STORE_FILE,
|
|
336
|
+
resolveReportStorePath,
|
|
337
|
+
JsonlReportStore
|
|
338
|
+
};
|
|
339
|
+
//# sourceMappingURL=chunk-PFXFQQRT.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/utils/expect-defined.ts
|
|
2
|
+
function expectDefined(value, label) {
|
|
3
|
+
if (value === null || value === void 0) {
|
|
4
|
+
const err = new Error(label ? `Expected ${label} to be defined` : "Expected value to be defined");
|
|
5
|
+
err.name = "ExpectDefinedError";
|
|
6
|
+
throw err;
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
expectDefined
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=chunk-PFZO3C6H.js.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/notifications/notifier-impl.ts
|
|
2
|
+
function emptyCounters() {
|
|
3
|
+
return { attempted: 0, delivered: 0, failed: 0, suppressed: 0 };
|
|
4
|
+
}
|
|
5
|
+
var NotifierImpl = class _NotifierImpl {
|
|
6
|
+
#channels = /* @__PURE__ */ new Map();
|
|
7
|
+
#counters = /* @__PURE__ */ new Map();
|
|
8
|
+
/** Soft cap for #counters — prevents unbounded growth if channels are
|
|
9
|
+
* repeatedly registered with distinct names. */
|
|
10
|
+
static #MAX_COUNTERS = 100;
|
|
11
|
+
// -----------------------------------------------------------------------
|
|
12
|
+
// Lifecycle
|
|
13
|
+
// -----------------------------------------------------------------------
|
|
14
|
+
notify(channels, message) {
|
|
15
|
+
const names = typeof channels === "string" ? [channels] : channels;
|
|
16
|
+
if (names.length === 0) return Promise.resolve([]);
|
|
17
|
+
const results = [];
|
|
18
|
+
for (const name of names) {
|
|
19
|
+
const channel = this.#channels.get(name);
|
|
20
|
+
if (!channel) continue;
|
|
21
|
+
const counters = this.#counters.get(name) ?? emptyCounters();
|
|
22
|
+
counters.attempted += 1;
|
|
23
|
+
this.#counters.set(name, counters);
|
|
24
|
+
results.push(
|
|
25
|
+
channel.deliver(message).then(
|
|
26
|
+
(result) => {
|
|
27
|
+
if (result.ok) {
|
|
28
|
+
counters.delivered += 1;
|
|
29
|
+
} else {
|
|
30
|
+
counters.failed += 1;
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
},
|
|
34
|
+
(err) => {
|
|
35
|
+
counters.failed += 1;
|
|
36
|
+
const error = err instanceof Error ? err.message : "unknown rejection";
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
channel: name,
|
|
40
|
+
error: `threw: ${error}`,
|
|
41
|
+
deliveredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return Promise.all(results);
|
|
48
|
+
}
|
|
49
|
+
registerChannel(channel) {
|
|
50
|
+
this.#channels.set(channel.name, channel);
|
|
51
|
+
if (!this.#counters.has(channel.name)) {
|
|
52
|
+
if (this.#counters.size >= _NotifierImpl.#MAX_COUNTERS) {
|
|
53
|
+
const oldest = this.#counters.keys().next();
|
|
54
|
+
if (!oldest.done && oldest.value !== void 0) {
|
|
55
|
+
this.#counters.delete(oldest.value);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
this.#counters.set(channel.name, emptyCounters());
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
unregisterChannel(name) {
|
|
62
|
+
this.#channels.delete(name);
|
|
63
|
+
}
|
|
64
|
+
async channels() {
|
|
65
|
+
const entries = Array.from(this.#channels.entries());
|
|
66
|
+
const results = await Promise.allSettled(
|
|
67
|
+
entries.map(async ([name, channel]) => {
|
|
68
|
+
try {
|
|
69
|
+
const ping = await channel.ping();
|
|
70
|
+
return { name, type: channel.type, ok: ping.ok, error: ping.error };
|
|
71
|
+
} catch {
|
|
72
|
+
return { name, type: channel.type, ok: false, error: "ping threw" };
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
return results.map(
|
|
77
|
+
(r) => r.status === "fulfilled" ? r.value : { name: "unknown", type: "unknown", ok: false, error: "unhandled ping rejection" }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
counters() {
|
|
81
|
+
let attempted = 0;
|
|
82
|
+
let delivered = 0;
|
|
83
|
+
let failed = 0;
|
|
84
|
+
let suppressed = 0;
|
|
85
|
+
for (const c of this.#counters.values()) {
|
|
86
|
+
attempted += c.attempted;
|
|
87
|
+
delivered += c.delivered;
|
|
88
|
+
failed += c.failed;
|
|
89
|
+
suppressed += c.suppressed;
|
|
90
|
+
}
|
|
91
|
+
return { attempted, delivered, failed, suppressed };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Return per-channel counters for detailed diagnostics.
|
|
95
|
+
* Not part of the `Notifier` interface — available on the concrete class.
|
|
96
|
+
*/
|
|
97
|
+
perChannelCounters() {
|
|
98
|
+
const result = {};
|
|
99
|
+
for (const [name, c] of this.#counters) {
|
|
100
|
+
result[name] = {
|
|
101
|
+
attempted: c.attempted,
|
|
102
|
+
delivered: c.delivered,
|
|
103
|
+
failed: c.failed
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
NotifierImpl
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=chunk-PHH7LD45.js.map
|