@tryinget/pi-agent-vent 0.1.2 → 0.2.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/README.md +10 -3
- package/docs/engineering.local.md +13 -0
- package/docs/project/2026-05-21-agent-vent-design.md +7 -5
- package/docs/project/2026-06-05-self-toolbox-agent-vent-diagnostic-boundary.md +69 -0
- package/docs/project/product-posture.md +2 -0
- package/docs/project/vision.md +4 -0
- package/extensions/agent-vent.ts +95 -13
- package/package.json +16 -7
- package/policy/engineering-lane.json +13 -1
- package/prompts/{implementation-planning.md → pi-agent-vent-implementation-planning.md} +3 -3
- package/prompts/{security-review.md → pi-agent-vent-security-review.md} +3 -3
- package/scripts/docs-list.sh +5 -0
- package/scripts/quality-gate.sh +5 -0
- package/scripts/release-artifact-check.mjs +35 -5
- package/scripts/release-check.sh +136 -55
- package/scripts/release-smoke-check.mjs +32 -1
- package/scripts/release-smoke.sh +17 -15
- package/scripts/validate-structure.mjs +8 -3
- package/scripts/validate-structure.sh +7 -2
- package/src/vent-store.js +85 -6
- package/tests/agent-vent-extension.test.js +190 -1
- package/tests/release-artifact-check.test.js +69 -2
- package/tests/release-smoke-check.test.js +25 -0
- package/tests/vent-store.test.js +43 -0
|
@@ -1,17 +1,25 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: tests agent_vent tool registration, commands, actions, and authority-safe output
|
|
3
|
+
// read_when:
|
|
4
|
+
// - modifying the extension schema, command surface, lifecycle actions, or smoke contracts
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import assert from "node:assert/strict";
|
|
2
8
|
import fs from "node:fs";
|
|
3
9
|
import os from "node:os";
|
|
4
10
|
import path from "node:path";
|
|
5
11
|
import test from "node:test";
|
|
6
|
-
import agentVentExtension from "../extensions/agent-vent.ts";
|
|
12
|
+
import agentVentExtension, { _test } from "../extensions/agent-vent.ts";
|
|
7
13
|
import { createCurationEvent, createVentRecord, readReviewEvents } from "../src/vent-store.js";
|
|
8
14
|
|
|
9
15
|
function createMockPi() {
|
|
10
16
|
const tools = new Map();
|
|
11
17
|
const commands = new Map();
|
|
18
|
+
const messages = [];
|
|
12
19
|
return {
|
|
13
20
|
tools,
|
|
14
21
|
commands,
|
|
22
|
+
messages,
|
|
15
23
|
api: {
|
|
16
24
|
registerTool(tool) {
|
|
17
25
|
tools.set(tool.name, tool);
|
|
@@ -19,6 +27,9 @@ function createMockPi() {
|
|
|
19
27
|
registerCommand(name, command) {
|
|
20
28
|
commands.set(name, command);
|
|
21
29
|
},
|
|
30
|
+
sendMessage(message) {
|
|
31
|
+
messages.push(message);
|
|
32
|
+
},
|
|
22
33
|
},
|
|
23
34
|
};
|
|
24
35
|
}
|
|
@@ -39,6 +50,9 @@ test("agent_vent tool schema stays aligned with retention candidate and compare
|
|
|
39
50
|
const schemaText = JSON.stringify(pi.tools.get("agent_vent").parameters);
|
|
40
51
|
|
|
41
52
|
assert.match(schemaText, /"compare"/);
|
|
53
|
+
assert.match(schemaText, /"preview"/);
|
|
54
|
+
assert.match(schemaText, /"workflow_friction"/);
|
|
55
|
+
assert.match(schemaText, /"context_alignment"/);
|
|
42
56
|
assert.match(schemaText, /"candidates"/);
|
|
43
57
|
assert.match(schemaText, /"history"/);
|
|
44
58
|
assert.match(schemaText, /retentionCandidateState/);
|
|
@@ -48,6 +62,41 @@ test("agent_vent tool schema stays aligned with retention candidate and compare
|
|
|
48
62
|
assert.match(schemaText, /outcomes, compare, export, or retention planning/);
|
|
49
63
|
});
|
|
50
64
|
|
|
65
|
+
test("agent_vent keeps long command output persistent while short statuses stay notifications", async () => {
|
|
66
|
+
const pi = createMockPi();
|
|
67
|
+
agentVentExtension(pi.api);
|
|
68
|
+
const notifications = [];
|
|
69
|
+
const ctx = { hasUI: true, ui: { notify: (message) => notifications.push(message) } };
|
|
70
|
+
|
|
71
|
+
await pi.commands.get("agent_vent").handler("help", ctx);
|
|
72
|
+
assert.equal(pi.messages.length, 1);
|
|
73
|
+
assert.equal(pi.messages[0].customType, "agent-vent-command");
|
|
74
|
+
assert.match(pi.messages[0].content, /agent_vent commands/);
|
|
75
|
+
assert.match(notifications[0], /added to the session transcript/);
|
|
76
|
+
|
|
77
|
+
await pi.commands.get("agent_vent").handler("unknown", ctx);
|
|
78
|
+
assert.equal(pi.messages.length, 1);
|
|
79
|
+
assert.match(notifications[1], /Unknown \/agent_vent action/);
|
|
80
|
+
assert.equal(_test.isLongCommandOutput("short"), false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("agent_vent cancellation fails before expensive store work", async () => {
|
|
84
|
+
const pi = createMockPi();
|
|
85
|
+
agentVentExtension(pi.api);
|
|
86
|
+
const controller = new AbortController();
|
|
87
|
+
controller.abort();
|
|
88
|
+
await assert.rejects(
|
|
89
|
+
() =>
|
|
90
|
+
pi.tools
|
|
91
|
+
.get("agent_vent")
|
|
92
|
+
.execute("cancelled", { action: "summary" }, controller.signal, undefined, {
|
|
93
|
+
cwd: "/repo",
|
|
94
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
95
|
+
}),
|
|
96
|
+
/cancelled/,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
51
100
|
test("agent_vent records minimized local diagnostics without external authority claims", async () => {
|
|
52
101
|
const pi = createMockPi();
|
|
53
102
|
agentVentExtension(pi.api);
|
|
@@ -474,6 +523,146 @@ test("agent_vent records minimized local diagnostics without external authority
|
|
|
474
523
|
}
|
|
475
524
|
});
|
|
476
525
|
|
|
526
|
+
test("agent_vent accepts category aliases for tool records and command filters", async () => {
|
|
527
|
+
const pi = createMockPi();
|
|
528
|
+
agentVentExtension(pi.api);
|
|
529
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-alias-"));
|
|
530
|
+
const oldDir = process.env.PI_AGENT_VENT_DIR;
|
|
531
|
+
const oldLog = console.log;
|
|
532
|
+
const messages = [];
|
|
533
|
+
process.env.PI_AGENT_VENT_DIR = dir;
|
|
534
|
+
console.log = (message) => messages.push(String(message));
|
|
535
|
+
try {
|
|
536
|
+
const tool = pi.tools.get("agent_vent");
|
|
537
|
+
const result = await tool.execute(
|
|
538
|
+
"tool-call-category-alias",
|
|
539
|
+
{
|
|
540
|
+
action: "record",
|
|
541
|
+
summary: "Workflow friction alias should normalize",
|
|
542
|
+
category: "workflow_friction",
|
|
543
|
+
severity: "medium",
|
|
544
|
+
},
|
|
545
|
+
undefined,
|
|
546
|
+
undefined,
|
|
547
|
+
{
|
|
548
|
+
cwd: "/repo",
|
|
549
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
550
|
+
},
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
assert.equal(result.details.record.category, "workflow");
|
|
554
|
+
assert.equal(
|
|
555
|
+
result.details.record.recurrenceKey,
|
|
556
|
+
"workflow:workflow-friction-alias-should-normalize",
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
const alignmentPreview = await tool.execute(
|
|
560
|
+
"tool-call-context-alignment-alias",
|
|
561
|
+
{
|
|
562
|
+
action: "preview",
|
|
563
|
+
summary: "Context alignment candidate should remain callable",
|
|
564
|
+
category: "context_alignment",
|
|
565
|
+
},
|
|
566
|
+
undefined,
|
|
567
|
+
undefined,
|
|
568
|
+
{
|
|
569
|
+
cwd: "/repo",
|
|
570
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
571
|
+
},
|
|
572
|
+
);
|
|
573
|
+
assert.equal(alignmentPreview.details.wouldRecord, true);
|
|
574
|
+
assert.equal(alignmentPreview.details.recordPreview.category, "other");
|
|
575
|
+
|
|
576
|
+
const reviewResult = await tool.execute(
|
|
577
|
+
"tool-call-category-alias-review",
|
|
578
|
+
{ action: "review", category: "workflow_friction" },
|
|
579
|
+
undefined,
|
|
580
|
+
undefined,
|
|
581
|
+
{
|
|
582
|
+
cwd: "/repo",
|
|
583
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
584
|
+
},
|
|
585
|
+
);
|
|
586
|
+
assert.equal(reviewResult.details.reviewQueue.filters.category, "workflow");
|
|
587
|
+
assert.equal(reviewResult.details.reviewQueue.matchingGroupCount, 1);
|
|
588
|
+
|
|
589
|
+
await pi.commands.get("agent_vent").handler("review category=workflow_friction", {
|
|
590
|
+
hasUI: false,
|
|
591
|
+
});
|
|
592
|
+
assert.match(messages[0], /Filters: category=workflow/);
|
|
593
|
+
assert.doesNotMatch(messages[0], /category=workflow_friction/);
|
|
594
|
+
} finally {
|
|
595
|
+
console.log = oldLog;
|
|
596
|
+
if (oldDir === undefined) delete process.env.PI_AGENT_VENT_DIR;
|
|
597
|
+
else process.env.PI_AGENT_VENT_DIR = oldDir;
|
|
598
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test("agent_vent preview runs anti-junk checks without writing records", async () => {
|
|
603
|
+
const pi = createMockPi();
|
|
604
|
+
agentVentExtension(pi.api);
|
|
605
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-preview-"));
|
|
606
|
+
const oldDir = process.env.PI_AGENT_VENT_DIR;
|
|
607
|
+
process.env.PI_AGENT_VENT_DIR = dir;
|
|
608
|
+
try {
|
|
609
|
+
const tool = pi.tools.get("agent_vent");
|
|
610
|
+
const preview = await tool.execute(
|
|
611
|
+
"tool-call-preview",
|
|
612
|
+
{
|
|
613
|
+
action: "preview",
|
|
614
|
+
summary: "Workflow friction alias should normalize before recording",
|
|
615
|
+
category: "workflow_friction",
|
|
616
|
+
tool: "self",
|
|
617
|
+
},
|
|
618
|
+
undefined,
|
|
619
|
+
undefined,
|
|
620
|
+
{
|
|
621
|
+
cwd: "/repo",
|
|
622
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
623
|
+
},
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
assert.equal(preview.details.wouldRecord, true);
|
|
627
|
+
assert.equal(preview.details.recordPreview.category, "workflow");
|
|
628
|
+
assert.match(preview.content[0].text, /No local diagnostic record was written/);
|
|
629
|
+
assert.equal(fs.existsSync(path.join(dir, "vents.jsonl")), false);
|
|
630
|
+
|
|
631
|
+
const junkPreview = await tool.execute(
|
|
632
|
+
"tool-call-preview-junk",
|
|
633
|
+
{ action: "preview", summary: "done" },
|
|
634
|
+
undefined,
|
|
635
|
+
undefined,
|
|
636
|
+
{
|
|
637
|
+
cwd: "/repo",
|
|
638
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
639
|
+
},
|
|
640
|
+
);
|
|
641
|
+
assert.equal(junkPreview.details.wouldRecord, false);
|
|
642
|
+
assert.match(junkPreview.content[0].text, /not recordable/);
|
|
643
|
+
|
|
644
|
+
await assert.rejects(
|
|
645
|
+
() =>
|
|
646
|
+
tool.execute(
|
|
647
|
+
"tool-call-record-junk",
|
|
648
|
+
{ action: "record", summary: "done" },
|
|
649
|
+
undefined,
|
|
650
|
+
undefined,
|
|
651
|
+
{
|
|
652
|
+
cwd: "/repo",
|
|
653
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
654
|
+
},
|
|
655
|
+
),
|
|
656
|
+
/anti-junk quality check/,
|
|
657
|
+
);
|
|
658
|
+
assert.equal(fs.existsSync(path.join(dir, "vents.jsonl")), false);
|
|
659
|
+
} finally {
|
|
660
|
+
if (oldDir === undefined) delete process.env.PI_AGENT_VENT_DIR;
|
|
661
|
+
else process.env.PI_AGENT_VENT_DIR = oldDir;
|
|
662
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
|
|
477
666
|
test("agent_vent command rejects unknown review filter keys without creating stores", async () => {
|
|
478
667
|
const pi = createMockPi();
|
|
479
668
|
agentVentExtension(pi.api);
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: tests package whitelist parsing and published markdown link validation
|
|
3
|
+
// read_when:
|
|
4
|
+
// - changing release artifact checks, pack output handling, or files-array policy
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import assert from "node:assert/strict";
|
|
2
8
|
import fs from "node:fs";
|
|
3
9
|
import os from "node:os";
|
|
@@ -77,6 +83,28 @@ test("pack json parser fails closed for malformed, stale, or unsafe pack output"
|
|
|
77
83
|
);
|
|
78
84
|
});
|
|
79
85
|
|
|
86
|
+
test("pack json parser accepts canonical arrays and npm 12 package-keyed objects", () => {
|
|
87
|
+
const entry = { name: "@tryinget/pi-agent-vent", files: [{ path: "package.json" }] };
|
|
88
|
+
assert.deepEqual(parsePackJson(JSON.stringify([entry])), ["package.json"]);
|
|
89
|
+
assert.deepEqual(parsePackJson(JSON.stringify({ "@tryinget/pi-agent-vent": entry })), [
|
|
90
|
+
"package.json",
|
|
91
|
+
]);
|
|
92
|
+
assert.throws(
|
|
93
|
+
() =>
|
|
94
|
+
parsePackJson(
|
|
95
|
+
JSON.stringify({
|
|
96
|
+
"@tryinget/pi-agent-vent": entry,
|
|
97
|
+
"@tryinget/other": entry,
|
|
98
|
+
}),
|
|
99
|
+
),
|
|
100
|
+
/Could not parse npm pack/,
|
|
101
|
+
);
|
|
102
|
+
assert.throws(
|
|
103
|
+
() => parsePackJson(JSON.stringify({ "@tryinget/wrong": entry })),
|
|
104
|
+
/Could not parse npm pack/,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
80
108
|
test("package files whitelist fails closed for wildcard entries", () => {
|
|
81
109
|
assert.throws(
|
|
82
110
|
() =>
|
|
@@ -114,13 +142,19 @@ test("package files whitelist detects missing and extra artifact entries", () =>
|
|
|
114
142
|
test("release artifact check accepts directory files entries and always-included metadata", () => {
|
|
115
143
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-release-check-ok-"));
|
|
116
144
|
try {
|
|
117
|
-
fs.writeFileSync(
|
|
145
|
+
fs.writeFileSync(
|
|
146
|
+
path.join(dir, "package.json"),
|
|
147
|
+
JSON.stringify({ name: "@tryinget/pi-agent-vent", version: "0.2.0", files: ["docs"] }),
|
|
148
|
+
);
|
|
118
149
|
fs.writeFileSync(path.join(dir, "README.md"), "[guide](docs/guide.md)\n");
|
|
119
150
|
fs.writeFileSync(path.join(dir, "LICENSE"), "local test license\n");
|
|
120
151
|
fs.mkdirSync(path.join(dir, "docs"));
|
|
121
152
|
fs.writeFileSync(path.join(dir, "docs", "guide.md"), "# Guide\n");
|
|
122
153
|
const packJsonText = JSON.stringify([
|
|
123
154
|
{
|
|
155
|
+
id: "@tryinget/pi-agent-vent@0.2.0",
|
|
156
|
+
name: "@tryinget/pi-agent-vent",
|
|
157
|
+
version: "0.2.0",
|
|
124
158
|
files: [
|
|
125
159
|
{ path: "README.md" },
|
|
126
160
|
{ path: "LICENSE" },
|
|
@@ -142,18 +176,51 @@ test("release artifact check accepts directory files entries and always-included
|
|
|
142
176
|
}
|
|
143
177
|
});
|
|
144
178
|
|
|
179
|
+
test("release artifact check rejects pack identity that differs from the local manifest", () => {
|
|
180
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-release-identity-"));
|
|
181
|
+
try {
|
|
182
|
+
fs.writeFileSync(
|
|
183
|
+
path.join(dir, "package.json"),
|
|
184
|
+
JSON.stringify({ name: "@tryinget/pi-agent-vent", version: "0.2.0", files: ["README.md"] }),
|
|
185
|
+
);
|
|
186
|
+
fs.writeFileSync(path.join(dir, "README.md"), "# Agent Vent\n");
|
|
187
|
+
const packJsonText = JSON.stringify([
|
|
188
|
+
{
|
|
189
|
+
id: "@tryinget/not-pi-agent-vent@999.0.0",
|
|
190
|
+
name: "@tryinget/not-pi-agent-vent",
|
|
191
|
+
version: "999.0.0",
|
|
192
|
+
files: [{ path: "README.md" }, { path: "package.json" }],
|
|
193
|
+
},
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
assert.throws(
|
|
197
|
+
() => runReleaseArtifactCheck({ cwd: dir, packJsonText, log: () => {} }),
|
|
198
|
+
/npm pack identity mismatch/,
|
|
199
|
+
);
|
|
200
|
+
} finally {
|
|
201
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
145
205
|
test("release artifact check reports packaged markdown failures with actionable wording", () => {
|
|
146
206
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-release-check-"));
|
|
147
207
|
try {
|
|
148
208
|
fs.writeFileSync(
|
|
149
209
|
path.join(dir, "package.json"),
|
|
150
|
-
JSON.stringify({
|
|
210
|
+
JSON.stringify({
|
|
211
|
+
name: "@tryinget/pi-agent-vent",
|
|
212
|
+
version: "0.2.0",
|
|
213
|
+
files: ["README.md", "docs/guide.md"],
|
|
214
|
+
}),
|
|
151
215
|
);
|
|
152
216
|
fs.mkdirSync(path.join(dir, "docs"));
|
|
153
217
|
fs.writeFileSync(path.join(dir, "README.md"), "[missing](docs/missing.md)\n");
|
|
154
218
|
fs.writeFileSync(path.join(dir, "docs", "guide.md"), "# Guide\n");
|
|
155
219
|
const packJsonText = JSON.stringify([
|
|
156
220
|
{
|
|
221
|
+
id: "@tryinget/pi-agent-vent@0.2.0",
|
|
222
|
+
name: "@tryinget/pi-agent-vent",
|
|
223
|
+
version: "0.2.0",
|
|
157
224
|
files: [{ path: "README.md" }, { path: "docs/guide.md" }, { path: "package.json" }],
|
|
158
225
|
},
|
|
159
226
|
]);
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: tests isolated release-smoke contracts for installed agent_vent artifacts
|
|
3
|
+
// read_when:
|
|
4
|
+
// - changing tarball source validation, package discovery, or path-tool smoke probes
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import assert from "node:assert/strict";
|
|
2
8
|
import fs from "node:fs";
|
|
3
9
|
import os from "node:os";
|
|
@@ -5,6 +11,7 @@ import path from "node:path";
|
|
|
5
11
|
import test from "node:test";
|
|
6
12
|
import {
|
|
7
13
|
assertAgentVentPathSmokeOutput,
|
|
14
|
+
assertExactHostContract,
|
|
8
15
|
assertInstalledArtifactPackage,
|
|
9
16
|
assertLocalTarballInstallSource,
|
|
10
17
|
assertPackageSpecInstalled,
|
|
@@ -13,6 +20,24 @@ import {
|
|
|
13
20
|
packageSourcesFromSettings,
|
|
14
21
|
} from "../scripts/release-smoke-check.mjs";
|
|
15
22
|
|
|
23
|
+
test("release smoke release-age bypass is gated by one exact host contract", () => {
|
|
24
|
+
const packageJson = {
|
|
25
|
+
devDependencies: {
|
|
26
|
+
"@earendil-works/pi-ai": "0.83.0",
|
|
27
|
+
"@earendil-works/pi-coding-agent": "0.83.0",
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
assert.equal(assertExactHostContract({ packageJson, hostVersion: "0.83.0" }), "0.83.0");
|
|
31
|
+
assert.throws(
|
|
32
|
+
() => assertExactHostContract({ packageJson, hostVersion: "^0.83.0" }),
|
|
33
|
+
/requires an exact Pi host version/,
|
|
34
|
+
);
|
|
35
|
+
assert.throws(
|
|
36
|
+
() => assertExactHostContract({ packageJson, hostVersion: "0.83.1" }),
|
|
37
|
+
/host contract mismatch/,
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
16
41
|
test("release smoke settings check accepts only unfiltered string package entries", () => {
|
|
17
42
|
const settings = {
|
|
18
43
|
packages: [
|
package/tests/vent-store.test.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: tests agent vent persistence, redaction, review projections, and retention safety
|
|
3
|
+
// read_when:
|
|
4
|
+
// - modifying vent-store records, filters, curation, exports, backups, or filesystem guards
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import assert from "node:assert/strict";
|
|
2
8
|
import { createHash } from "node:crypto";
|
|
3
9
|
import fs from "node:fs";
|
|
@@ -111,6 +117,43 @@ test("createVentRecord minimizes, redacts, and derives recurrence key", () => {
|
|
|
111
117
|
assert.deepEqual(tagOnlySecret.privacy.redactionPatterns, ["assigned_secret"]);
|
|
112
118
|
});
|
|
113
119
|
|
|
120
|
+
test("category aliases normalize at record and review-filter boundaries", () => {
|
|
121
|
+
const workflow = createVentRecord({
|
|
122
|
+
summary: "Workflow alias should not degrade",
|
|
123
|
+
category: "workflow_friction",
|
|
124
|
+
recurrenceKey: "workflow alias",
|
|
125
|
+
});
|
|
126
|
+
const missing = createVentRecord({
|
|
127
|
+
summary: "Missing affordance alias should preserve meaning",
|
|
128
|
+
category: "missing_affordance",
|
|
129
|
+
});
|
|
130
|
+
const alignment = createVentRecord({
|
|
131
|
+
summary: "Context alignment candidate should remain callable",
|
|
132
|
+
category: "context_alignment",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
assert.equal(workflow.category, "workflow");
|
|
136
|
+
assert.equal(workflow.recurrenceKey, "workflow:workflow-alias");
|
|
137
|
+
assert.equal(missing.category, "missing_capability");
|
|
138
|
+
assert.match(missing.recurrenceKey, /^missing_capability:/);
|
|
139
|
+
assert.equal(alignment.category, "other");
|
|
140
|
+
assert.match(alignment.recurrenceKey, /^other:/);
|
|
141
|
+
|
|
142
|
+
const queue = summarizeReviewQueue([workflow, missing, alignment], [], {
|
|
143
|
+
filters: { category: "workflow_friction" },
|
|
144
|
+
});
|
|
145
|
+
assert.equal(queue.filters.category, "workflow");
|
|
146
|
+
assert.equal(queue.matchingGroupCount, 1);
|
|
147
|
+
assert.equal(queue.items[0].recurrenceKey, workflow.recurrenceKey);
|
|
148
|
+
|
|
149
|
+
const alignmentQueue = summarizeReviewQueue([workflow, missing, alignment], [], {
|
|
150
|
+
filters: { category: "context_alignment" },
|
|
151
|
+
});
|
|
152
|
+
assert.equal(alignmentQueue.filters.category, "other");
|
|
153
|
+
assert.equal(alignmentQueue.matchingGroupCount, 1);
|
|
154
|
+
assert.equal(alignmentQueue.items[0].recurrenceKey, alignment.recurrenceKey);
|
|
155
|
+
});
|
|
156
|
+
|
|
114
157
|
test("explicit recurrence keys are redacted before slugging", () => {
|
|
115
158
|
const record = createVentRecord({
|
|
116
159
|
summary: "Explicit key should not leak",
|