@monotykamary/pi-vcc 0.3.17 → 0.4.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.
Files changed (47) hide show
  1. package/README.md +5 -4
  2. package/npm-shrinkwrap.json +2 -2
  3. package/package.json +1 -1
  4. package/src/core/build-sections.ts +12 -131
  5. package/src/core/format.ts +4 -14
  6. package/src/core/summarize.ts +98 -22
  7. package/src/sections.ts +0 -4
  8. package/__tests__/helpers.ts +0 -135
  9. package/__tests__/root-causes.test.ts +0 -858
  10. package/demo.gif +0 -0
  11. package/knip.json +0 -4
  12. package/scripts/audit-sessions.ts +0 -88
  13. package/scripts/benchmark-real-sessions.ts +0 -25
  14. package/scripts/compare-before-after.ts +0 -36
  15. package/scripts/dump-branch-output.ts +0 -20
  16. package/tests/australian-government.test.ts +0 -479
  17. package/tests/before-compact-hook.test.ts +0 -185
  18. package/tests/before-compact.test.ts +0 -227
  19. package/tests/brief.test.ts +0 -215
  20. package/tests/build-sections.test.ts +0 -59
  21. package/tests/compile.test.ts +0 -89
  22. package/tests/content.test.ts +0 -66
  23. package/tests/deep-extraction.test.ts +0 -313
  24. package/tests/extract-goals.test.ts +0 -86
  25. package/tests/extract-preferences.test.ts +0 -30
  26. package/tests/filter-noise.test.ts +0 -61
  27. package/tests/fixtures.ts +0 -61
  28. package/tests/format-recall.test.ts +0 -87
  29. package/tests/format.test.ts +0 -73
  30. package/tests/government-domain.test.ts +0 -542
  31. package/tests/lineage.test.ts +0 -33
  32. package/tests/load-messages.test.ts +0 -49
  33. package/tests/model-threshold-hook.test.ts +0 -311
  34. package/tests/model-threshold.test.ts +0 -223
  35. package/tests/normalize.test.ts +0 -104
  36. package/tests/proactive-threshold.test.ts +0 -490
  37. package/tests/real-sessions.test.ts +0 -38
  38. package/tests/recall-expand.test.ts +0 -15
  39. package/tests/recall-scope.test.ts +0 -32
  40. package/tests/recall-tool-scope.test.ts +0 -67
  41. package/tests/render-entries.test.ts +0 -92
  42. package/tests/report.test.ts +0 -58
  43. package/tests/sanitize.test.ts +0 -24
  44. package/tests/search-entries.test.ts +0 -233
  45. package/tests/support/load-session.ts +0 -23
  46. package/tests/support/real-sessions.ts +0 -51
  47. package/tests/threshold-integration.test.ts +0 -335
package/demo.gif DELETED
Binary file
package/knip.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "$schema": "https://unpkg.com/knip@5/schema.json",
3
- "ignore": ["tests/**", "scripts/**"]
4
- }
@@ -1,88 +0,0 @@
1
- import { basename, dirname } from "node:path";
2
- import { compile } from "../src/core/summarize";
3
- import { normalize } from "../src/core/normalize";
4
- import { filterNoise } from "../src/core/filter-noise";
5
- import { renderMessage } from "../src/core/render-entries";
6
- import { prepareSessionSamples } from "../tests/support/real-sessions";
7
- import { loadSessionMessages } from "../tests/support/load-session";
8
-
9
- const SEP = "=".repeat(80);
10
- const samples = await prepareSessionSamples(10);
11
-
12
- for (const sample of samples) {
13
- const loaded = loadSessionMessages(sample.copy);
14
- const { messages } = loaded;
15
-
16
- const rawBlocks = normalize(messages);
17
- const filteredBlocks = filterNoise(rawBlocks);
18
- const afterText = compile({ messages });
19
-
20
- const rendered = messages.map((m, i) => renderMessage(m, i));
21
- const beforeChars = rendered.reduce((s, e) => s + e.summary.length, 0);
22
-
23
- const project = dirname(sample.source).split("--").filter(Boolean).pop() ?? "unknown";
24
-
25
- const goalSection = afterText.match(/\[Session Goal\]\n([\s\S]*?)(?=\n\n\[|$)/)?.[1] ?? "(empty)";
26
- const stateSection = afterText.match(/\[Current State\]\n([\s\S]*?)(?=\n\n\[|$)/)?.[1] ?? "(empty)";
27
- const doneSection = afterText.match(/\[What Was Done\]\n([\s\S]*?)(?=\n\n\[|$)/)?.[1] ?? "(empty)";
28
- const problemsSection = afterText.match(/\[Open Problems\]\n([\s\S]*?)(?=\n\n\[|$)/)?.[1] ?? "(empty)";
29
- const nextSection = afterText.match(/\[Next Best Steps\]\n([\s\S]*?)(?=\n\n\[|$)/)?.[1] ?? "(empty)";
30
-
31
- const doneLines = doneSection.split("\n").filter(l => l.trim());
32
- const problemLines = problemsSection.split("\n").filter(l => l.trim());
33
-
34
- // Detect issues
35
- const issues: string[] = [];
36
-
37
- // 1. Goal quality
38
- const goalLines = goalSection.split("\n").map(l => l.replace(/^- /, "").trim()).filter(Boolean);
39
- if (goalLines[0] && goalLines[0].length < 5) issues.push(`GOAL_TOO_SHORT: "${goalLines[0]}"`);
40
- if (goalLines.length === 0) issues.push("GOAL_EMPTY");
41
-
42
- // 2. Sensitive data in What Was Done
43
- if (/sshpass|password|secret|token=|api[_-]?key/i.test(doneSection)) {
44
- issues.push("SENSITIVE_DATA_IN_DONE");
45
- }
46
-
47
- // 3. Raw code/minified JS in summary
48
- if (/\{[a-zA-Z$_]+:[a-zA-Z$_]+,[a-zA-Z$_]+:/.test(afterText) || /var [a-zA-Z]+=/.test(afterText)) {
49
- issues.push("RAW_CODE_LEAK");
50
- }
51
-
52
- // 4. Open problems count
53
- if (problemLines.length > 10) issues.push(`PROBLEMS_OVERCOUNT: ${problemLines.length}`);
54
-
55
- // 5. Next steps empty
56
- if (nextSection === "(empty)") issues.push("NEXT_STEPS_EMPTY");
57
-
58
- // 6. What Was Done too verbose
59
- if (doneLines.length > 15) issues.push(`DONE_TOO_VERBOSE: ${doneLines.length} lines`);
60
-
61
- // 7. Summary too large (>10K chars)
62
- if (afterText.length > 10000) issues.push(`SUMMARY_TOO_LARGE: ${afterText.length} chars`);
63
-
64
- console.log(SEP);
65
- console.log(`PROJECT: ${project}`);
66
- console.log(`FILE: ${basename(sample.source)}`);
67
- console.log(`Size: ${(sample.size / 1024).toFixed(0)}KB | Msgs: ${messages.length} | Blocks raw: ${rawBlocks.length} -> filtered: ${filteredBlocks.length}`);
68
- console.log(`Before: ${beforeChars} chars | After: ${afterText.length} chars | Ratio: ${(beforeChars / afterText.length).toFixed(1)}x`);
69
- console.log(`Issues: ${issues.length === 0 ? "NONE" : issues.join(", ")}`);
70
- console.log("");
71
- console.log("--- GOAL ---");
72
- console.log(goalSection.slice(0, 300));
73
- console.log("");
74
- console.log("--- CURRENT STATE (first 300c) ---");
75
- console.log(stateSection.slice(0, 300));
76
- console.log("");
77
- console.log("--- WHAT WAS DONE (first 5 lines) ---");
78
- console.log(doneLines.slice(0, 5).join("\n"));
79
- console.log(`... (${doneLines.length} total lines)`);
80
- console.log("");
81
- console.log("--- OPEN PROBLEMS (first 5 lines) ---");
82
- console.log(problemLines.slice(0, 5).join("\n"));
83
- console.log(`... (${problemLines.length} total lines)`);
84
- console.log("");
85
- console.log("--- NEXT STEPS ---");
86
- console.log(nextSection.slice(0, 300));
87
- console.log("");
88
- }
@@ -1,25 +0,0 @@
1
- import { performance } from "node:perf_hooks";
2
- import { basename } from "node:path";
3
- import { buildCompactReport } from "../src/core/report";
4
- import { prepareSessionSamples } from "../tests/support/real-sessions";
5
- import { loadSessionMessages } from "../tests/support/load-session";
6
-
7
- const samples = await prepareSessionSamples(2);
8
- for (const sample of samples) {
9
- const loaded = loadSessionMessages(sample.copy);
10
- const start = performance.now();
11
- const report = buildCompactReport({ messages: loaded.messages });
12
- const elapsedMs = performance.now() - start;
13
- console.log(JSON.stringify({
14
- sourceFile: basename(sample.source),
15
- sourceSizeBytes: sample.size,
16
- copiedToTemp: true,
17
- loadedMessages: loaded.messageCount,
18
- skippedMessages: loaded.skippedCount,
19
- compileMs: Number(elapsedMs.toFixed(2)),
20
- before: report.before,
21
- after: report.after,
22
- compression: report.compression,
23
- recall: report.recall,
24
- }, null, 2));
25
- }
@@ -1,36 +0,0 @@
1
- import { basename } from "node:path";
2
- import { compile } from "../src/core/summarize";
3
- import { renderMessage } from "../src/core/render-entries";
4
- import { clip } from "../src/core/content";
5
- import { prepareSessionSamples } from "../tests/support/real-sessions";
6
- import { loadSessionMessages } from "../tests/support/load-session";
7
-
8
- const SEP = "=".repeat(80);
9
- const samples = await prepareSessionSamples(2);
10
-
11
- for (const sample of samples) {
12
- const loaded = loadSessionMessages(sample.copy);
13
- const { messages } = loaded;
14
-
15
- const rendered = messages.map((m, i) => renderMessage(m, i));
16
- const beforeLines = rendered.map(
17
- (e) => `#${e.index} [${e.role}] ${clip(e.summary, 300)}`,
18
- );
19
- const beforeText = beforeLines.join("\n");
20
- const afterText = compile({ messages });
21
-
22
- console.log(SEP);
23
- console.log(`FILE: ${basename(sample.source)}`);
24
- console.log(`Messages: ${messages.length} | Before chars: ${beforeText.length} | After chars: ${afterText.length}`);
25
- console.log(`Compression: ${(beforeText.length / afterText.length).toFixed(1)}x`);
26
- console.log(SEP);
27
-
28
- console.log("\n--- BEFORE (raw context, first 40 + last 20 entries) ---\n");
29
- for (const line of beforeLines.slice(0, 40)) console.log(line);
30
- if (beforeLines.length > 60) console.log(`\n... (${beforeLines.length - 60} entries omitted) ...\n`);
31
- for (const line of beforeLines.slice(-20)) console.log(line);
32
-
33
- console.log("\n--- AFTER (pi-vcc compiled summary) ---\n");
34
- console.log(afterText);
35
- console.log("\n");
36
- }
@@ -1,20 +0,0 @@
1
- import { basename } from "node:path";
2
- import { compile } from "../src/core/summarize";
3
- import { prepareSessionSamples } from "../tests/support/real-sessions";
4
- import { loadSessionMessages } from "../tests/support/load-session";
5
- import { writeFileSync, mkdirSync } from "node:fs";
6
-
7
- const outDir = "/tmp/pi-vcc-compare";
8
- const branch = process.argv[2] || "unknown";
9
- mkdirSync(`${outDir}/${branch}`, { recursive: true });
10
-
11
- const samples = await prepareSessionSamples(5);
12
- for (const sample of samples) {
13
- const name = basename(sample.source).slice(0, 30);
14
- const loaded = loadSessionMessages(sample.copy);
15
- const summary = compile({ messages: loaded.messages });
16
- const outFile = `${outDir}/${branch}/${name}.txt`;
17
- writeFileSync(outFile, summary);
18
- console.log(`${name} (${loaded.messageCount} msgs) => ${summary.length} chars`);
19
- }
20
- console.log(`\nSaved to ${outDir}/${branch}/`);
@@ -1,479 +0,0 @@
1
- import { describe, it, expect } from "bun:test";
2
- import { mkdtempSync, writeFileSync, rmSync } from "fs";
3
- import { tmpdir } from "os";
4
- import { join } from "path";
5
- import { searchEntries } from "../src/core/search-entries";
6
- import { formatRecallOutput } from "../src/core/format-recall";
7
- import { registerRecallTool } from "../src/tools/recall";
8
- import type { RenderedEntry } from "../src/core/render-entries";
9
- import type { Message } from "@earendil-works/pi-ai";
10
- import type { SearchHit } from "../src/core/search-entries";
11
-
12
- // ── Australian government session fixture ──
13
- // Uses British/Commonwealth spelling and Australian government frameworks:
14
- // ISM (Information Security Manual), PSPF (Protective Security Policy Framework),
15
- // IRAP (Information Security Registered Assessors Program), Essential Eight,
16
- // ASD (Australian Signals Directorate), DTA (Digital Transformation Agency),
17
- // Privacy Act 1988, Australian Privacy Principles (APPs)
18
-
19
- const ausEntries: RenderedEntry[] = [
20
- { index: 0, role: "user", summary: "Update the ISM compliance documentation for the new Essential Eight controls" },
21
- { index: 1, role: "assistant", summary: "Reviewing the ASD Information Security Manual and Essential Eight maturity model for compliance alignment.", files: ["security/ism-compliance-2027.md"] },
22
- { index: 2, role: "tool_result", summary: "[Read] security/ism-compliance-2027.md" },
23
- { index: 3, role: "assistant", summary: "The current ISM compliance documentation covers the Essential Eight at Maturity Level 2. I'll update for Level 3 requirements." },
24
- { index: 4, role: "user", summary: "Ensure the authorisation controls meet PSPF requirements" },
25
- { index: 5, role: "assistant", summary: "The PSPF requires multi-factor authorisation for all privileged access. Updating authorisation policy to align with PSPF Policy 9.", files: ["security/authorisation-policy.md"] },
26
- { index: 6, role: "tool_result", summary: "[Edit] security/authorisation-policy.md" },
27
- { index: 7, role: "user", summary: "Organise the IRAP assessment for the cloud service" },
28
- { index: 8, role: "assistant", summary: "Scheduling IRAP assessment with an ASD-certified assessor for the cloud service. The programme includes documentation review and testing phases." },
29
- { index: 9, role: "user", summary: "Check the Privacy Act 1988 compliance for the new data handling procedures" },
30
- { index: 10, role: "assistant", summary: "Cross-referencing data handling procedures against Australian Privacy Principles under the Privacy Act 1988. APP 6 and APP 8 require authorisation for overseas disclosure." },
31
- { index: 11, role: "user", summary: "Update the protective security policy with the new risk management framework" },
32
- { index: 12, role: "assistant", summary: "Updating protective security policy to include the new risk management programme per PSPF Policy 1 and the ANAO guidance.", files: ["security/protective-policy.md"] },
33
- { index: 13, role: "tool_result", summary: "[Edit] security/protective-policy.md" },
34
- { index: 14, role: "user", summary: "Review the DTA digital service standard compliance" },
35
- { index: 15, role: "assistant", summary: "The Digital Transformation Agency service standard requires accessibility, privacy impact assessment, and user-centred design. All 13 criteria are being assessed." },
36
- { index: 16, role: "user", summary: "Analyse the cyber security incident response procedures" },
37
- { index: 17, role: "assistant", summary: "Analysing incident response procedures against the ASD Cyber Security Centre guidance. The current procedures cover reporting, containment, and recovery. Defence in depth measures are documented." },
38
- { index: 18, role: "user", summary: "Submit the security clearance pool documentation for review" },
39
- { index: 19, role: "assistant", summary: "Security clearance pool documentation has been submitted for formal review. NV1 and NV2 clearance records are current through the AGSVA programme." },
40
- { index: 20, role: "user", summary: "Catalogue the information management framework references" },
41
- { index: 21, role: "assistant", summary: "Cataloguing information management references across the National Archives of Australia, Records Authorities, and the Digital Continuity 2025 programme." },
42
- ];
43
-
44
- const ausMessages: Message[] = [
45
- { role: "user", content: "Update the ISM compliance documentation for the new Essential Eight controls" } as any,
46
- { role: "assistant", content: [{ type: "text", text: "Reviewing the ASD Information Security Manual and Essential Eight maturity model for compliance alignment." }] } as any,
47
- { role: "toolResult", content: [{ type: "text", text: "ISM compliance doc: Essential Eight controls at Maturity Level 2" }], toolName: "Read", isError: false } as any,
48
- { role: "assistant", content: [{ type: "text", text: "The current ISM compliance documentation covers the Essential Eight at Maturity Level 2. I'll update for Level 3 requirements." }] } as any,
49
- { role: "user", content: "Ensure the authorisation controls meet PSPF requirements" } as any,
50
- { role: "assistant", content: [{ type: "text", text: "The PSPF requires multi-factor authorisation for all privileged access. Updating authorisation policy to align with PSPF Policy 9." }] } as any,
51
- { role: "toolResult", content: [{ type: "text", text: "File edited: updated authorisation section per PSPF Policy 9" }], toolName: "Edit", isError: false } as any,
52
- { role: "user", content: "Organise the IRAP assessment for the cloud service" } as any,
53
- { role: "assistant", content: [{ type: "text", text: "Scheduling IRAP assessment with an ASD-certified assessor for the cloud service. The programme includes documentation review and testing phases." }] } as any,
54
- { role: "user", content: "Check the Privacy Act 1988 compliance for the new data handling procedures" } as any,
55
- { role: "assistant", content: [{ type: "text", text: "Cross-referencing data handling procedures against Australian Privacy Principles under the Privacy Act 1988. APP 6 and APP 8 require authorisation for overseas disclosure." }] } as any,
56
- { role: "user", content: "Update the protective security policy with the new risk management framework" } as any,
57
- { role: "assistant", content: [{ type: "text", text: "Updating protective security policy to include the new risk management programme per PSPF Policy 1 and the ANAO guidance." }] } as any,
58
- { role: "toolResult", content: [{ type: "text", text: "File edited: added risk management programme section" }], toolName: "Edit", isError: false } as any,
59
- { role: "user", content: "Review the DTA digital service standard compliance" } as any,
60
- { role: "assistant", content: [{ type: "text", text: "The Digital Transformation Agency service standard requires accessibility, privacy impact assessment, and user-centred design. All 13 criteria are being assessed." }] } as any,
61
- { role: "user", content: "Analyse the cyber security incident response procedures" } as any,
62
- { role: "assistant", content: [{ type: "text", text: "Analysing incident response procedures against the ASD Cyber Security Centre guidance. The current procedures cover reporting, containment, and recovery. Defence in depth measures are documented." }] } as any,
63
- { role: "user", content: "Submit the security clearance pool documentation for review" } as any,
64
- { role: "assistant", content: [{ type: "text", text: "Security clearance pool documentation has been submitted for formal review. NV1 and NV2 clearance records are current through the AGSVA programme." }] } as any,
65
- { role: "user", content: "Catalogue the information management framework references" } as any,
66
- { role: "assistant", content: [{ type: "text", text: "Cataloguing information management references across the National Archives of Australia, Records Authorities, and the Digital Continuity 2025 programme." }] } as any,
67
- ];
68
-
69
- // ── Helpers ──
70
-
71
- const register = () => {
72
- let tool: any;
73
- registerRecallTool({ registerTool: (t: any) => { tool = t; } } as any);
74
- return tool;
75
- };
76
-
77
- const makeAusSession = () => {
78
- const dir = mkdtempSync(join(tmpdir(), "pi-vcc-aus-gov-"));
79
- const file = join(dir, "session.jsonl");
80
- const lines = ausMessages.map((msg, i) =>
81
- JSON.stringify({ type: "message", id: `m${i}`, message: msg })
82
- );
83
- writeFileSync(file, lines.join("\n") + "\n", "utf8");
84
- return { dir, file };
85
- };
86
-
87
- const invoke = async (tool: any, file: string, params: Record<string, unknown>, branchIds?: string[]) => {
88
- const ids = branchIds ?? ausMessages.map((_, i) => `m${i}`);
89
- const result = await tool.execute("tool-call", params, undefined, undefined, {
90
- sessionManager: {
91
- getSessionFile: () => file,
92
- getBranch: () => ids.map((id) => ({ id })),
93
- getEntries: () => ids.map((id) => ({ id })),
94
- },
95
- });
96
- return result.content[0].text as string;
97
- };
98
-
99
- // ── Tests ──
100
-
101
- describe("Australian government: spelling variant search", () => {
102
- it("US 'authorization' finds British 'authorisation'", () => {
103
- const r = searchEntries(ausEntries, ausMessages, "authorization");
104
- expect(r.length).toBeGreaterThanOrEqual(2);
105
- // Should match entries #4 and #5 (authorisation controls and PSPF)
106
- expect(r.some((h) => h.index === 4 || h.index === 5)).toBe(true);
107
- });
108
-
109
- it("British 'authorisation' also finds British 'authorisation'", () => {
110
- const r = searchEntries(ausEntries, ausMessages, "authorisation");
111
- expect(r.length).toBeGreaterThanOrEqual(2);
112
- });
113
-
114
- it("US 'organize' finds British 'organise'", () => {
115
- const entries: RenderedEntry[] = [
116
- { index: 0, role: "user", summary: "Organise the security review" },
117
- { index: 1, role: "assistant", summary: "Arranging the security review schedule" },
118
- ];
119
- const msgs: Message[] = entries.map((e) => ({
120
- role: e.role === "user" ? "user" : "assistant" as any,
121
- content: e.summary,
122
- } as any));
123
-
124
- const r = searchEntries(entries, msgs, "organize");
125
- expect(r).toHaveLength(1);
126
- expect(r[0].index).toBe(0);
127
- });
128
-
129
- it("US 'analyze' finds British 'analyse'", () => {
130
- // Entry #16 has "Analyse", entry #17 has "Analysing"
131
- const r = searchEntries(ausEntries, ausMessages, "analyze");
132
- expect(r.length).toBeGreaterThanOrEqual(1);
133
- expect(r.some((h) => h.index === 16 || h.index === 17)).toBe(true);
134
- });
135
-
136
- it("US 'program' finds British 'programme'", () => {
137
- // Entries #8, #12, #19, #21 use "programme"
138
- const r = searchEntries(ausEntries, ausMessages, "program");
139
- expect(r.length).toBeGreaterThanOrEqual(2);
140
- expect(r.some((h) => [8, 12, 19, 21].includes(h.index))).toBe(true);
141
- });
142
-
143
- it("US 'defense' finds British 'defence'", () => {
144
- // Entry #17 mentions "Defence in depth"
145
- const r = searchEntries(ausEntries, ausMessages, "defense");
146
- expect(r.length).toBeGreaterThanOrEqual(1);
147
- });
148
-
149
- it("US 'color' finds British 'colour' in government context", () => {
150
- const entries: RenderedEntry[] = [
151
- { index: 0, role: "user", summary: "Update the colour coding for security classifications" },
152
- { index: 1, role: "assistant", summary: "Updating colour coding: TOP SECRET (red), SECRET (blue), PROTECTED (green)" },
153
- ];
154
- const msgs: Message[] = entries.map((e) => ({
155
- role: e.role === "user" ? "user" : "assistant" as any,
156
- content: e.summary,
157
- } as any));
158
-
159
- const r = searchEntries(entries, msgs, "color");
160
- expect(r).toHaveLength(2);
161
- });
162
-
163
- it("US 'catalog' finds British 'catalogue'", () => {
164
- // Entry #20 has "Catalogue"
165
- const r = searchEntries(ausEntries, ausMessages, "catalog");
166
- expect(r.length).toBeGreaterThanOrEqual(1);
167
- expect(r.some((h) => h.index === 20 || h.index === 21)).toBe(true);
168
- });
169
-
170
- it("US 'license' finds British 'licence' in government context", () => {
171
- const entries: RenderedEntry[] = [
172
- { index: 0, role: "user", summary: "Check the software licence compliance" },
173
- { index: 1, role: "assistant", summary: "All software licences are current and compliant." },
174
- ];
175
- const msgs: Message[] = entries.map((e) => ({
176
- role: e.role === "user" ? "user" : "assistant" as any,
177
- content: e.summary,
178
- } as any));
179
-
180
- // US spelling "license" should match British "licence"
181
- const r = searchEntries(entries, msgs, "license");
182
- expect(r).toHaveLength(2);
183
-
184
- // British "licence" should also work
185
- const r2 = searchEntries(entries, msgs, "licence");
186
- expect(r2).toHaveLength(2);
187
- });
188
-
189
- it("multi-term British spelling query crosses US/British boundary", () => {
190
- // Query uses US spellings, data uses British
191
- const r = searchEntries(ausEntries, ausMessages, "authorization PSPF compliance");
192
- // 3 meaningful terms → MIN_TERM_MATCH_FOR_MULTITERM=2
193
- // "authorization" matches "authorisation", "PSPF" is specific, "compliance" is common
194
- expect(r.length).toBeGreaterThan(0);
195
- expect(r.some((h) => h.index === 4 || h.index === 5)).toBe(true);
196
- });
197
-
198
- it("spelling expansion does not affect regex queries", () => {
199
- // Regex queries (containing metacharacters) are passed through as-is;
200
- // spelling expansion only applies to natural language terms.
201
- const entries: RenderedEntry[] = [
202
- { index: 0, role: "user", summary: "authorisation required" },
203
- { index: 1, role: "user", summary: "authorization pending" },
204
- ];
205
- const msgs: Message[] = entries.map((e) => ({
206
- role: "user" as any, content: e.summary,
207
- } as any));
208
-
209
- // Natural language query (no metacharacters) → expansion applied → finds both
210
- const rNl = searchEntries(entries, msgs, "authorisation");
211
- expect(rNl).toHaveLength(2);
212
-
213
- // Regex query (| metacharacter) → NO expansion → both match via explicit alternation
214
- const rRegex = searchEntries(entries, msgs, "authorisation|authorization");
215
- expect(rRegex).toHaveLength(2);
216
-
217
- // Regex query matching only British spelling → should NOT auto-expand
218
- // Using [is]ation as a regex class to match only "isation" literal
219
- const rRegexOnlyIse = searchEntries(entries, msgs, "authoris[ai]tion");
220
- expect(rRegexOnlyIse).toHaveLength(1);
221
- expect(rRegexOnlyIse[0].index).toBe(0); // matches British only
222
- });
223
- });
224
-
225
- describe("Australian government: searchEntries", () => {
226
- it("high-overlap Australian government vocabulary stays bounded", () => {
227
- // "security policy compliance review" — all common in Australian gov sessions
228
- const r = searchEntries(ausEntries, ausMessages, "security policy compliance review");
229
- expect(r.length).toBeLessThan(ausEntries.length);
230
- expect(r.length).toBeGreaterThan(0);
231
- // With 4 meaningful terms → ≥2 matches required
232
- for (const hit of r) {
233
- expect(hit.matchCount).toBeGreaterThanOrEqual(2);
234
- }
235
- });
236
-
237
- it("specific Australian framework term returns narrow results", () => {
238
- // "IRAP" is specific to Australian government, appears once
239
- const r = searchEntries(ausEntries, ausMessages, "IRAP");
240
- expect(r.length).toBeLessThanOrEqual(3);
241
- });
242
-
243
- it("Essential Eight query returns targeted results", () => {
244
- const r = searchEntries(ausEntries, ausMessages, "Essential Eight");
245
- expect(r.length).toBeGreaterThanOrEqual(1);
246
- expect(r.some((h) => h.index === 0 || h.index === 1)).toBe(true);
247
- });
248
-
249
- it("Australian Privacy Act specific search", () => {
250
- const r = searchEntries(ausEntries, ausMessages, "Privacy Act 1988");
251
- expect(r.length).toBeGreaterThanOrEqual(1);
252
- expect(r.some((h) => h.index === 9 || h.index === 10)).toBe(true);
253
- });
254
-
255
- it("PSPF policy reference search", () => {
256
- const r = searchEntries(ausEntries, ausMessages, "PSPF Policy");
257
- expect(r.length).toBeGreaterThanOrEqual(1);
258
- // PSPF Policy 9 is in #5, PSPF Policy 1 is in #12
259
- expect(r.some((h) => [5, 12].includes(h.index))).toBe(true);
260
- });
261
-
262
- it("AGSVA clearance query returns targeted results", () => {
263
- const r = searchEntries(ausEntries, ausMessages, "AGSVA");
264
- expect(r.length).toBeGreaterThanOrEqual(1);
265
- expect(r.some((h) => h.index === 19)).toBe(true);
266
- });
267
-
268
- it("DTA digital service standard search", () => {
269
- const r = searchEntries(ausEntries, ausMessages, "Digital Transformation Agency");
270
- expect(r.length).toBeGreaterThanOrEqual(1);
271
- expect(r.some((h) => h.index === 15)).toBe(true);
272
- });
273
-
274
- it("National Archives of Australia reference search", () => {
275
- const r = searchEntries(ausEntries, ausMessages, "National Archives");
276
- expect(r.length).toBeGreaterThanOrEqual(1);
277
- expect(r.some((h) => h.index === 21)).toBe(true);
278
- });
279
-
280
- it("ANAO audit reference search", () => {
281
- const r = searchEntries(ausEntries, ausMessages, "ANAO");
282
- expect(r.length).toBeGreaterThanOrEqual(1);
283
- expect(r.some((h) => h.index === 12)).toBe(true);
284
- });
285
-
286
- it("Australian government session: BM25 ranks specific framework hits highest", () => {
287
- // A query with both specific (ASD) and generic (security) terms
288
- const r = searchEntries(ausEntries, ausMessages, "ASD security guidance");
289
- // 3 terms → ≥2 required. "ASD" is rare (high IDF), "security" is common (low IDF).
290
- // Top result should prioritise the ASD-specific entries.
291
- expect(r.length).toBeGreaterThan(0);
292
- // First result should be an ASD-heavy entry
293
- expect(r[0].matchCount).toBeGreaterThanOrEqual(2);
294
- });
295
- });
296
-
297
- describe("Australian government: vcc_recall integration", () => {
298
- it("US-spelled query finds British content through integration", async () => {
299
- const { dir, file } = makeAusSession();
300
- try {
301
- const tool = register();
302
-
303
- // Model searching with "authorization" against British "authorisation"
304
- const result = await invoke(tool, file, { query: "authorization PSPF" });
305
- expect(result).toContain("matches");
306
- // Should have found the British-spelled entries
307
- expect(result).toMatch(/authorisation|PSPF/i);
308
- } finally {
309
- rmSync(dir, { recursive: true, force: true });
310
- }
311
- });
312
-
313
- it("Australian framework term returns paginated results", async () => {
314
- const { dir, file } = makeAusSession();
315
- try {
316
- const tool = register();
317
-
318
- const result = await invoke(tool, file, { query: "security compliance policy review" });
319
- // Should show results, not dump the whole session
320
- expect(result).toMatch(/matches/);
321
- } finally {
322
- rmSync(dir, { recursive: true, force: true });
323
- }
324
- });
325
-
326
- it("IRAP search returns narrow results through tool", async () => {
327
- const { dir, file } = makeAusSession();
328
- try {
329
- const tool = register();
330
-
331
- const result = await invoke(tool, file, { query: "IRAP assessment" });
332
- expect(result).toContain("matches");
333
- expect(result).toMatch(/IRAP/);
334
- } finally {
335
- rmSync(dir, { recursive: true, force: true });
336
- }
337
- });
338
-
339
- it("Essential Eight search finds ASD framework entries", async () => {
340
- const { dir, file } = makeAusSession();
341
- try {
342
- const tool = register();
343
-
344
- const result = await invoke(tool, file, { query: "Essential Eight controls" });
345
- expect(result).toContain("matches");
346
- expect(result).toMatch(/Essential Eight|ISM/i);
347
- } finally {
348
- rmSync(dir, { recursive: true, force: true });
349
- }
350
- });
351
-
352
- it("search for 'organise' also finds entries via British spelling", async () => {
353
- const { dir, file } = makeAusSession();
354
- try {
355
- const tool = register();
356
-
357
- // Both spellings should find #7/#8 (organise/organize)
358
- const result = await invoke(tool, file, { query: "organize IRAP" });
359
- expect(result).toMatch(/matches|IRAP/);
360
- } finally {
361
- rmSync(dir, { recursive: true, force: true });
362
- }
363
- });
364
- });
365
-
366
- describe("Australian government: stress scenarios", () => {
367
- it("defence portfolio session with Commonwealth spelling", () => {
368
- const entries: RenderedEntry[] = [
369
- { index: 0, role: "user", summary: "Review the Defence procurement authorisation framework" },
370
- { index: 1, role: "assistant", summary: "Analysing the Defence procurement authorisation framework against the Commonwealth Procurement Rules." },
371
- { index: 2, role: "user", summary: "Check behaviour against the Defence behaviour and values framework" },
372
- { index: 3, role: "assistant", summary: "Behaviour standards are set by the Defence Values and Behaviour Framework. All personnel must adhere." },
373
- { index: 4, role: "user", summary: "Update the catalogue of defence equipment authorisations" },
374
- { index: 5, role: "assistant", summary: "Cataloguing defence equipment authorisations per the Defence and Industry Programme. Honour system requirements apply." },
375
- { index: 6, role: "user", summary: "Organise the offshore licence compliance review" },
376
- { index: 7, role: "assistant", summary: "Organising offshore licence compliance review against the Foreign Investment Review Board requirements and Defence Trade Controls Act." },
377
- ];
378
- const msgs: Message[] = entries.map((e) => ({
379
- role: e.role === "user" ? "user" : "assistant" as any,
380
- content: e.summary,
381
- } as any));
382
-
383
- // US spelling → British hits
384
- expect(searchEntries(entries, msgs, "authorization").length).toBeGreaterThanOrEqual(2);
385
- expect(searchEntries(entries, msgs, "defense").length).toBeGreaterThanOrEqual(2);
386
- expect(searchEntries(entries, msgs, "behavior").length).toBeGreaterThanOrEqual(2);
387
- expect(searchEntries(entries, msgs, "catalog").length).toBeGreaterThanOrEqual(2);
388
- expect(searchEntries(entries, msgs, "organize").length).toBeGreaterThanOrEqual(1);
389
- expect(searchEntries(entries, msgs, "license").length).toBeGreaterThanOrEqual(1);
390
-
391
- // Mixed spelling query: terms from both conventions
392
- const r = searchEntries(entries, msgs, "authorization defense behavior");
393
- // 3 terms → requires ≥2. "authorization" matches authorisation,
394
- // "defense" matches defence, "behavior" matches behaviour.
395
- expect(r.length).toBeGreaterThan(0);
396
- });
397
-
398
- it("state government session with mixed US/British spelling", () => {
399
- // Government workers often mix spellings: "authorisation" in policy
400
- // documents but "authorization" in technical contexts (OAuth, etc.)
401
- const entries: RenderedEntry[] = [
402
- { index: 0, role: "user", summary: "Check the OAuth authorization server configuration" },
403
- { index: 1, role: "assistant", summary: "The OAuth authorization server is configured per standard. Authorisation for API access follows PSPF guidelines." },
404
- { index: 2, role: "user", summary: "Update the data classification programme documentation" },
405
- { index: 3, role: "assistant", summary: "Updating the data classification program per ISM guidance. Both programme and program references are retained for compatibility." },
406
- ];
407
- const msgs: Message[] = entries.map((e) => ({
408
- role: e.role === "user" ? "user" : "assistant" as any,
409
- content: e.summary,
410
- } as any));
411
-
412
- // Either spelling should find the relevant entries
413
- const fromUs = searchEntries(entries, msgs, "authorization");
414
- const fromBr = searchEntries(entries, msgs, "authorisation");
415
- // Both should find entries mentioning authorization/authorisation
416
- expect(fromUs.length).toBeGreaterThanOrEqual(1);
417
- expect(fromBr.length).toBeGreaterThanOrEqual(1);
418
-
419
- // programme/program cross-match
420
- const programUs = searchEntries(entries, msgs, "program");
421
- expect(programUs.length).toBeGreaterThanOrEqual(2);
422
- });
423
-
424
- it("large Australian government session stays bounded", () => {
425
- // Simulate a 150-entry Australian government session with heavy
426
- // domain vocabulary overlap and Commonwealth spelling
427
- const bigEntries: RenderedEntry[] = Array.from({ length: 150 }, (_, i) => ({
428
- index: i,
429
- role: (i % 2 === 0 ? "user" : "assistant") as any,
430
- summary: i % 2 === 0
431
- ? `Review security policy compliance authorisation for department ${Math.floor(i / 2)}`
432
- : `Processed defence programme policy compliance review for section ${Math.floor(i / 2)}`,
433
- }));
434
- const bigMsgs: Message[] = bigEntries.map((e) => ({
435
- role: e.role === "user" ? "user" : "assistant" as any,
436
- content: e.summary,
437
- } as any));
438
-
439
- // US-spelled query against British-spelled data
440
- const rUs = searchEntries(bigEntries, bigMsgs, "authorization defense program");
441
- expect(rUs.length).toBeLessThanOrEqual(50); // MAX_SEARCH_RESULTS
442
-
443
- // British-spelled query
444
- const rBr = searchEntries(bigEntries, bigMsgs, "authorisation defence programme");
445
- expect(rBr.length).toBeLessThanOrEqual(50);
446
-
447
- // Both should return similar counts (US/British should be transparent)
448
- expect(Math.abs(rUs.length - rBr.length)).toBeLessThanOrEqual(5);
449
- });
450
-
451
- it("ASD Essential Eight maturity model search", () => {
452
- const entries: RenderedEntry[] = [
453
- { index: 0, role: "user", summary: "Assess Essential Eight Maturity Level 3 compliance" },
454
- { index: 1, role: "assistant", summary: "Assessing Essential Eight Maturity Level 3: application control, patch management, and multi-factor authentication requirements per ASD guidance." },
455
- { index: 2, role: "user", summary: "What are the gaps at Maturity Level 2?" },
456
- { index: 3, role: "assistant", summary: "At Maturity Level 2 gaps include: application control partial coverage, Microsoft Office macro configuration, and operating system patch authorisation delays." },
457
- { index: 4, role: "user", summary: "Organise the remediation programme" },
458
- { index: 5, role: "assistant", summary: "Organising the Essential Eight remediation programme. Priority: macro configuration and patch management authorisation workflows." },
459
- ];
460
- const msgs: Message[] = entries.map((e) => ({
461
- role: e.role === "user" ? "user" : "assistant" as any,
462
- content: e.summary,
463
- } as any));
464
-
465
- // US spelling "authorization" matching British "authorisation"
466
- const r = searchEntries(entries, msgs, "Essential Eight authorization");
467
- // "Essential" and "Eight" are specific, "authorization" matches "authorisation"
468
- expect(r.length).toBeGreaterThan(0);
469
-
470
- // Specific framework search
471
- const lvl3 = searchEntries(entries, msgs, "Maturity Level 3");
472
- expect(lvl3.length).toBeGreaterThanOrEqual(1);
473
- expect(lvl3.some((h) => h.index === 0 || h.index === 1)).toBe(true);
474
-
475
- // "organize" (US) matches "organise" (British) in entry #4/#5
476
- const org = searchEntries(entries, msgs, "organize remediation");
477
- expect(org.length).toBeGreaterThanOrEqual(1);
478
- });
479
- });