@tryinget/pi-agent-vent 0.2.2 → 0.3.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.
@@ -0,0 +1,378 @@
1
+ /**
2
+ summary: "Vent store review outcomes, comparisons, fail-closed paths, and escalation drafts; split from vent-store.test.js."
3
+ read_when:
4
+ - "You change review outcomes, comparisons, fail-closed paths, and escalation drafts behavior."
5
+ */
6
+ import assert from "node:assert/strict";
7
+ import fs from "node:fs";
8
+ import os from "node:os";
9
+ import path from "node:path";
10
+ import test from "node:test";
11
+ import {
12
+ appendCurationEvent,
13
+ buildEscalationDraft,
14
+ buildReviewComparison,
15
+ buildReviewDetail,
16
+ buildReviewOutcomes,
17
+ createCurationEvent,
18
+ createReviewEvent,
19
+ createVentRecord,
20
+ DRAFT_TARGETS,
21
+ formatReviewComparison,
22
+ formatReviewDetail,
23
+ formatReviewOutcomes,
24
+ readCurationEvents,
25
+ readVentRecords,
26
+ } from "../src/vent-store.js";
27
+
28
+ test("review outcomes bucket post-review follow-up without authority drift", () => {
29
+ const fresh = createVentRecord({
30
+ summary: "Fresh outcome needs review",
31
+ category: "workflow",
32
+ recurrenceKey: "fresh outcome",
33
+ tags: ["outcome"],
34
+ });
35
+ const acknowledged = createVentRecord({
36
+ summary: "Acknowledged package issue",
37
+ category: "tool_failure",
38
+ recurrenceKey: "acknowledged outcome",
39
+ packageName: "@tryinget/pi-agent-vent",
40
+ tags: ["outcome"],
41
+ });
42
+ const dismissed = createVentRecord({
43
+ summary: "Dismissed noisy group",
44
+ category: "documentation",
45
+ recurrenceKey: "dismissed outcome",
46
+ tags: ["outcome"],
47
+ });
48
+ const drafted = createVentRecord({
49
+ summary: "Escalation draft prepared",
50
+ category: "bug",
51
+ recurrenceKey: "drafted outcome",
52
+ severity: "high",
53
+ tags: ["outcome"],
54
+ });
55
+ const reviews = [
56
+ createReviewEvent({
57
+ recurrenceKey: acknowledged.recurrenceKey,
58
+ state: "acknowledged",
59
+ note: "seen token=abc123 locally",
60
+ }),
61
+ createReviewEvent({ recurrenceKey: dismissed.recurrenceKey, state: "dismissed" }),
62
+ createReviewEvent({ recurrenceKey: drafted.recurrenceKey, state: "escalation_drafted" }),
63
+ ];
64
+
65
+ const outcomes = buildReviewOutcomes({
66
+ records: [fresh, acknowledged, dismissed, drafted],
67
+ reviewEvents: reviews,
68
+ filters: { tags: ["outcome"] },
69
+ limit: 10,
70
+ });
71
+ assert.equal(outcomes.counts.new, 1);
72
+ assert.equal(outcomes.counts.acknowledged, 1);
73
+ assert.equal(outcomes.counts.dismissed, 1);
74
+ assert.equal(outcomes.counts.escalation_drafted, 1);
75
+ assert.equal(outcomes.limitPerBucket, 10);
76
+ assert.deepEqual(outcomes.filters.tags, ["outcome"]);
77
+ const boundedFilter = buildReviewOutcomes({
78
+ records: [fresh],
79
+ reviewEvents: [],
80
+ filters: { tags: ["x".repeat(1000)] },
81
+ });
82
+ assert.deepEqual(boundedFilter.filters.tags, ["x".repeat(80)]);
83
+ assert.equal(boundedFilter.matchingGroupCount, 0);
84
+
85
+ const text = formatReviewOutcomes(outcomes);
86
+ assert.match(text, /Agent vent review outcomes/);
87
+ assert.match(text, /showing up to 10 group\(s\) per state bucket/);
88
+ assert.match(text, /new: 1 group/);
89
+ assert.match(text, /acknowledged: 1 group/);
90
+ assert.match(text, /dismissed: 1 group/);
91
+ assert.match(text, /escalation_drafted: 1 group/);
92
+ assert.match(text, /decision posture: needs_local_review/);
93
+ assert.match(text, /decision posture: dismissed_retention_ready/);
94
+ assert.match(text, /decision posture: draft_recorded_owner_external/);
95
+ assert.match(text, /seen token=\[REDACTED\] locally/);
96
+ assert.match(text, /retention waits for local review/);
97
+ assert.match(text, /optional local lifecycle: \/agent_vent retention preview/);
98
+ assert.match(
99
+ text,
100
+ /export this outcome bucket: \/agent_vent export markdown acknowledged tag=outcome/,
101
+ );
102
+ assert.match(text, /Local diagnostic labels only; not owner routing/);
103
+ assert.match(text, /No AK task, GitHub issue, incident, evidence, telemetry/);
104
+ assert.doesNotMatch(
105
+ text,
106
+ /GitHub issue was created|AK task was created|incident was declared|owner was assigned|resolved externally|was filed/,
107
+ );
108
+ });
109
+
110
+ test("review comparison contrasts state buckets without authority drift", () => {
111
+ const fresh = createVentRecord({
112
+ summary: "Fresh compare needs review token=abc123",
113
+ category: "workflow",
114
+ recurrenceKey: "fresh compare",
115
+ tags: ["Compare"],
116
+ });
117
+ const acknowledged = createVentRecord({
118
+ summary: "Acknowledged compare issue",
119
+ category: "tool_failure",
120
+ severity: "high",
121
+ recurrenceKey: "ack compare",
122
+ tags: ["Compare"],
123
+ tool: "pi reload",
124
+ packageName: "@tryinget/pi-agent-vent",
125
+ });
126
+ const dismissed = createVentRecord({
127
+ summary: "Dismissed compare issue",
128
+ category: "documentation",
129
+ recurrenceKey: "dismiss compare",
130
+ tags: ["Compare"],
131
+ });
132
+ const drafted = createVentRecord({
133
+ summary: "Drafted compare critical issue",
134
+ category: "bug",
135
+ severity: "critical",
136
+ recurrenceKey: "draft compare",
137
+ tags: ["Compare"],
138
+ });
139
+ const reviews = [
140
+ createReviewEvent({ recurrenceKey: acknowledged.recurrenceKey, state: "acknowledged" }),
141
+ createReviewEvent({ recurrenceKey: dismissed.recurrenceKey, state: "dismissed" }),
142
+ createReviewEvent({ recurrenceKey: drafted.recurrenceKey, state: "escalation_drafted" }),
143
+ ];
144
+
145
+ const comparison = buildReviewComparison({
146
+ records: [fresh, acknowledged, dismissed, drafted],
147
+ reviewEvents: reviews,
148
+ filters: { tags: ["compare"], tool: `${"x".repeat(5000)} token=abc123` },
149
+ limit: 1000,
150
+ });
151
+ assert.equal(comparison.limitPerState, 100);
152
+ assert.equal(comparison.matchingGroupCount, 0);
153
+ assert.ok(comparison.filters.tool.length <= 80);
154
+
155
+ const unfiltered = buildReviewComparison({
156
+ records: [fresh, acknowledged, dismissed, drafted],
157
+ reviewEvents: reviews,
158
+ filters: { tags: ["compare"] },
159
+ limit: 2,
160
+ });
161
+ assert.equal(unfiltered.totals.new.groups, 1);
162
+ assert.equal(unfiltered.totals.acknowledged.candidateIncidents, 0);
163
+ assert.equal(unfiltered.totals.escalation_drafted.candidateIncidents, 1);
164
+ assert.equal(unfiltered.totals.escalation_drafted.criticalGroups, 1);
165
+ assert.deepEqual(unfiltered.filters.tags, ["compare"]);
166
+ const text = formatReviewComparison(unfiltered);
167
+ assert.match(text, /Agent vent review comparison/);
168
+ assert.match(text, /State totals:/);
169
+ assert.match(text, /export bucket: \/agent_vent export markdown acknowledged tag=compare/);
170
+ assert.match(text, /outcomes: \/agent_vent outcomes acknowledged tag=compare/);
171
+ assert.match(
172
+ text,
173
+ /retention planning: \/agent_vent retention candidates acknowledged tag=compare/,
174
+ );
175
+ assert.match(text, /choose local outcome: \/agent_vent review set acknowledged/);
176
+ assert.match(text, /intentionally emits no archive or restore confirmation tokens/);
177
+ assert.match(text, /No AK task, GitHub issue, incident, evidence, telemetry/);
178
+ assert.match(text, /token=\[REDACTED\]/);
179
+ assert.doesNotMatch(
180
+ text,
181
+ /archive:[a-f0-9]|restore:[a-f0-9]|GitHub issue was created|AK task was created|incident was declared|owner was assigned|resolved externally|was filed/,
182
+ );
183
+ });
184
+
185
+ test("review outcomes limit is explicit per state bucket", () => {
186
+ const states = ["new", "acknowledged", "dismissed", "escalation_drafted"];
187
+ const records = [];
188
+ const reviews = [];
189
+ for (const state of states) {
190
+ const record = createVentRecord({
191
+ summary: `${state} grouped`,
192
+ recurrenceKey: `${state}-group`,
193
+ });
194
+ records.push(record);
195
+ if (state !== "new") {
196
+ reviews.push(createReviewEvent({ recurrenceKey: record.recurrenceKey, state }));
197
+ }
198
+ }
199
+
200
+ const outcomes = buildReviewOutcomes({ records, reviewEvents: reviews, state: "all", limit: 1 });
201
+ assert.equal(outcomes.limitPerBucket, 1);
202
+ assert.deepEqual(
203
+ outcomes.buckets.map((bucket) => `${bucket.state}:${bucket.items.length}`),
204
+ ["new:1", "acknowledged:1", "dismissed:1", "escalation_drafted:1"],
205
+ );
206
+ assert.match(formatReviewOutcomes(outcomes), /showing up to 1 group\(s\) per state bucket/);
207
+ });
208
+
209
+ test("review outcomes fail closed for invalid states and category filters", () => {
210
+ assert.throws(
211
+ () => buildReviewOutcomes({ state: "resolved", records: [], reviewEvents: [] }),
212
+ /invalid agent_vent review state/,
213
+ );
214
+ assert.throws(
215
+ () => buildReviewOutcomes({ filters: { category: "../../etc/passwd" } }),
216
+ /invalid agent_vent review filter category/,
217
+ );
218
+ const emptyText = formatReviewOutcomes(buildReviewOutcomes({ records: [], reviewEvents: [] }));
219
+ assert.match(emptyText, /No agent vent records found/);
220
+ assert.match(emptyText, /Boundary:/);
221
+ });
222
+
223
+ test("review detail fails closed for unknown groups and redacts legacy JSONL", () => {
224
+ assert.throws(
225
+ () => buildReviewDetail({ recurrenceKey: "bug:missing", records: [] }),
226
+ /cannot inspect unknown recurrence group/,
227
+ );
228
+
229
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-review-detail-"));
230
+ const storePath = path.join(dir, "vents.jsonl");
231
+ fs.writeFileSync(
232
+ storePath,
233
+ `${JSON.stringify({
234
+ schemaVersion: 1,
235
+ id: "legacy",
236
+ createdAt: "2026-05-21T00:00:00.000Z",
237
+ category: "bug",
238
+ severity: "high",
239
+ recurrenceKey: "bug:legacy-secret",
240
+ summary: "Legacy token=abc123 should redact",
241
+ evidence: "Authorization Bearer abcdefghijklmnop",
242
+ })}\n`,
243
+ "utf8",
244
+ );
245
+
246
+ const records = readVentRecords(storePath).records;
247
+ const text = formatReviewDetail(
248
+ buildReviewDetail({ recurrenceKey: "bug:legacy-secret", records, limit: 1000 }),
249
+ );
250
+ assert.doesNotMatch(text, /abc123|abcdefghijklmnop/);
251
+ assert.match(text, /token=\[REDACTED\]/);
252
+ assert.match(text, /Bearer \[REDACTED\]/);
253
+ });
254
+
255
+ test("curation JSONL tolerates malformed lines and symlink stores fail closed", () => {
256
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-curation-"));
257
+ const filePath = path.join(dir, "curation-events.jsonl");
258
+ fs.writeFileSync(filePath, "{not-json}\n", "utf8");
259
+ appendCurationEvent(
260
+ filePath,
261
+ createCurationEvent({
262
+ action: "rename",
263
+ sourceRecurrenceKey: "bug:old",
264
+ targetRecurrenceKey: "bug:new",
265
+ }),
266
+ );
267
+ const result = readCurationEvents(filePath);
268
+ assert.equal(result.malformedLines, 1);
269
+ assert.equal(result.events.length, 1);
270
+
271
+ const target = path.join(dir, "target.jsonl");
272
+ const link = path.join(dir, "curation-link.jsonl");
273
+ fs.writeFileSync(target, "", "utf8");
274
+ fs.symlinkSync(target, link);
275
+ assert.throws(() => appendCurationEvent(link, result.events[0]), /must not be a symlink/);
276
+ });
277
+
278
+ test("draft-only escalation text is bounded and authority-safe", () => {
279
+ const records = Array.from({ length: 8 }, (_, index) =>
280
+ createVentRecord(
281
+ {
282
+ summary: `Draft sample ${index}`,
283
+ evidence: `Pointer ${index}`,
284
+ category: "bug",
285
+ severity: index === 7 ? "high" : "medium",
286
+ recurrenceKey: "draft-target",
287
+ tool: index % 2 === 0 ? "pi reload token=abc123" : undefined,
288
+ packageName: "@tryinget/pi-agent-vent",
289
+ tags: ["Draft Flow"],
290
+ },
291
+ { id: `v${index}`, now: `2026-05-21T00:0${index}:00.000Z` },
292
+ ),
293
+ );
294
+ const review = createReviewEvent({
295
+ recurrenceKey: "bug:draft-target",
296
+ state: "acknowledged",
297
+ note: "reviewed token=abc123",
298
+ });
299
+ const draft = buildEscalationDraft({
300
+ target: "github_issue",
301
+ recurrenceKey: "bug:draft-target",
302
+ records,
303
+ reviewEvents: [review],
304
+ limit: 3,
305
+ now: "2026-05-21T00:10:00.000Z",
306
+ });
307
+
308
+ assert.equal(draft.samples.length, 3);
309
+ assert.equal(draft.group.reviewState, "acknowledged");
310
+ assert.match(draft.text, /Draft-only GitHub issue text/);
311
+ assert.match(draft.text, /No AK task, GitHub issue, incident, evidence/);
312
+ assert.match(draft.text, /Local diagnostic facets \(not owner routing\)/);
313
+ assert.match(draft.text, /Tools: pi-reload-token-redacted/);
314
+ assert.match(draft.text, /Packages: tryinget-pi-agent-vent/);
315
+ assert.match(draft.text, /token=\[REDACTED\]/);
316
+ assert.doesNotMatch(draft.text, /abc123|was created|was filed|was declared/);
317
+ });
318
+
319
+ test("draft-only escalation supports all targets and curation projections", () => {
320
+ assert.deepEqual(DRAFT_TARGETS, [
321
+ "github_issue",
322
+ "ak_task",
323
+ "incident_review",
324
+ "maintainer_note",
325
+ ]);
326
+ const first = createVentRecord(
327
+ {
328
+ summary: "Primary draft",
329
+ category: "workflow",
330
+ recurrenceKey: "draft-primary",
331
+ tool: "pi reload",
332
+ packageName: "@tryinget/pi-agent-vent",
333
+ },
334
+ { id: "v1" },
335
+ );
336
+ const second = createVentRecord(
337
+ {
338
+ summary: "Duplicate draft",
339
+ category: "workflow",
340
+ recurrenceKey: "draft-dupe",
341
+ tool: "pi toolbox",
342
+ packageName: "@tryinget/pi-toolbox-discovery",
343
+ },
344
+ { id: "v2" },
345
+ );
346
+ const curation = createCurationEvent({
347
+ action: "merge",
348
+ sourceRecurrenceKey: second.recurrenceKey,
349
+ targetRecurrenceKey: first.recurrenceKey,
350
+ });
351
+
352
+ for (const target of DRAFT_TARGETS) {
353
+ const draft = buildEscalationDraft({
354
+ target,
355
+ recurrenceKey: second.recurrenceKey,
356
+ records: [first, second],
357
+ curationEvents: [curation],
358
+ });
359
+ assert.equal(draft.recurrenceKey, first.recurrenceKey);
360
+ assert.equal(draft.group.count, 2);
361
+ assert.deepEqual(draft.group.tools, ["pi-reload", "pi-toolbox"]);
362
+ assert.match(draft.text, /Draft-only/);
363
+ assert.match(draft.text, /Local facets are hints, not owner routing/);
364
+ }
365
+ });
366
+
367
+ test("draft-only escalation fails closed for unknown group and invalid target", () => {
368
+ const records = [createVentRecord({ summary: "Known", category: "bug" }, { id: "v1" })];
369
+ assert.throws(
370
+ () => buildEscalationDraft({ target: "github_issue", recurrenceKey: "bug:missing", records }),
371
+ /unknown recurrence group/,
372
+ );
373
+ assert.throws(
374
+ () =>
375
+ buildEscalationDraft({ target: "email", recurrenceKey: records[0].recurrenceKey, records }),
376
+ /invalid agent_vent draft target/,
377
+ );
378
+ });
@@ -1,55 +0,0 @@
1
- #!/usr/bin/env bash
2
- # ---
3
- # summary: locates and invokes documentation listing support for pi-agent-vent
4
- # read_when:
5
- # - enumerating package docs or overriding the docs-list implementation
6
- # ---
7
- set -euo pipefail
8
-
9
- ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
10
- DEFAULT_SCRIPT="$HOME/ai-society/core/agent-scripts/scripts/docs-list.mjs"
11
- LOCAL_FALLBACK_SCRIPT="$ROOT_DIR/scripts/docs-list.mjs"
12
-
13
- usage() {
14
- cat <<'USAGE'
15
- Usage: ./scripts/docs-list.sh [docs-list args]
16
-
17
- Resolves docs-list script in this order:
18
- 1) DOCS_LIST_SCRIPT env var (absolute/relative path)
19
- 2) Local fallback: ./scripts/docs-list.mjs
20
- 3) Default global path: ~/ai-society/core/agent-scripts/scripts/docs-list.mjs
21
-
22
- Examples:
23
- ./scripts/docs-list.sh
24
- ./scripts/docs-list.sh --workspace --discover
25
- ./scripts/docs-list.sh --json
26
- USAGE
27
- }
28
-
29
- if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
30
- usage
31
- exit 0
32
- fi
33
-
34
- if ! command -v node >/dev/null 2>&1; then
35
- echo "Error: node is required to run docs-list." >&2
36
- exit 1
37
- fi
38
-
39
- SCRIPT_PATH=""
40
-
41
- if [[ -n "${DOCS_LIST_SCRIPT:-}" ]]; then
42
- SCRIPT_PATH="${DOCS_LIST_SCRIPT}"
43
- elif [[ -f "$LOCAL_FALLBACK_SCRIPT" ]]; then
44
- SCRIPT_PATH="$LOCAL_FALLBACK_SCRIPT"
45
- else
46
- SCRIPT_PATH="$DEFAULT_SCRIPT"
47
- fi
48
-
49
- if [[ ! -f "$SCRIPT_PATH" ]]; then
50
- echo "Error: docs-list script not found: $SCRIPT_PATH" >&2
51
- echo "Set DOCS_LIST_SCRIPT to your docs-list.mjs path, or place docs-list script at: $DEFAULT_SCRIPT" >&2
52
- exit 1
53
- fi
54
-
55
- node "$SCRIPT_PATH" "$@"