kushi-agents 6.2.0 → 6.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.
- package/package.json +1 -1
- package/plugin/runners/pull-email.mjs +3 -194
- package/plugin/runners/pull-meetings.mjs +4 -207
- package/plugin/runners/pull-onenote.mjs +3 -239
- package/plugin/runners/pull-sharepoint.mjs +3 -284
- package/plugin/runners/pull-teams.mjs +3 -170
- package/plugin/runners/test/fixtures/refresh-dir/email.json +4 -7
- package/plugin/runners/test/fixtures/refresh-dir/teams.json +4 -6
- package/plugin/runners/test/integration/csc-pull.integration.test.mjs +160 -0
- package/plugin/runners/test/fixtures/email-abn-amro.json +0 -13
- package/plugin/runners/test/fixtures/email-novel-error.json +0 -9
- package/plugin/runners/test/fixtures/meetings-abn-amro.json +0 -10
- package/plugin/runners/test/fixtures/meetings-body-unavailable.json +0 -10
- package/plugin/runners/test/fixtures/onenote-abn-amro.json +0 -30
- package/plugin/runners/test/fixtures/onenote-partial.json +0 -21
- package/plugin/runners/test/fixtures/sharepoint-abn-amro.json +0 -12
- package/plugin/runners/test/fixtures/teams-abn-amro.json +0 -11
- package/plugin/runners/test/integration/pull-email.integration.test.mjs +0 -149
- package/plugin/runners/test/integration/pull-meetings.integration.test.mjs +0 -92
- package/plugin/runners/test/integration/pull-onenote.integration.test.mjs +0 -86
- package/plugin/runners/test/integration/pull-sharepoint.integration.test.mjs +0 -93
- package/plugin/runners/test/integration/pull-teams.integration.test.mjs +0 -91
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"meeting": {
|
|
3
|
-
"id": "MSoxMjM0NTY3OA==",
|
|
4
|
-
"subject": "ABN AMRO — Architecture Review",
|
|
5
|
-
"startDateTime": "2026-05-26T15:00:00Z",
|
|
6
|
-
"endDateTime": "2026-05-26T16:00:00Z",
|
|
7
|
-
"joinWebUrl": "https://teams.microsoft.com/meet/31827546911768?p=YOgZHuaffeF0MGBCNt"
|
|
8
|
-
},
|
|
9
|
-
"transcript": "WEBVTT\n\n00:00:00.000 --> 00:00:05.000\nAlice: Welcome everyone.\n\n00:00:05.000 --> 00:00:10.000\nBob: Thanks. Let's start with the agenda.\n\n00:00:10.000 --> 00:00:20.000\nCarol: Architecture diagram is ready for review.\n"
|
|
10
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"meeting": {
|
|
3
|
-
"id": "MSoxMjM0NTY3OA==",
|
|
4
|
-
"subject": "ABN AMRO — Discovery (transcript pending)",
|
|
5
|
-
"startDateTime": "2026-05-28T09:00:00Z",
|
|
6
|
-
"endDateTime": "2026-05-28T10:00:00Z",
|
|
7
|
-
"joinWebUrl": "https://teams.microsoft.com/meet/26457824867211?p=zdSjg6cqHoHPA8Hx11"
|
|
8
|
-
},
|
|
9
|
-
"transcript": null
|
|
10
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"section": {
|
|
3
|
-
"id": "015a0ac9-58f4-4f34-98b3-a60075b36627",
|
|
4
|
-
"displayName": "ABN AMRO"
|
|
5
|
-
},
|
|
6
|
-
"resolvedSection": {
|
|
7
|
-
"id": "015a0ac9-58f4-4f34-98b3-a60075b36627",
|
|
8
|
-
"displayName": "ABN AMRO"
|
|
9
|
-
},
|
|
10
|
-
"pages": [
|
|
11
|
-
{
|
|
12
|
-
"id": "p1",
|
|
13
|
-
"title": "Kickoff Notes",
|
|
14
|
-
"lastModifiedDateTime": "2026-05-25T10:00:00Z",
|
|
15
|
-
"content": "<html><body><h1>Kickoff</h1><p>Project goals discussed.</p></body></html>"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"id": "p2",
|
|
19
|
-
"title": "Architecture Notes",
|
|
20
|
-
"lastModifiedDateTime": "2026-05-27T14:00:00Z",
|
|
21
|
-
"content": "<html><body><h1>Architecture</h1><p>Three-tier proposed.</p></body></html>"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"id": "p3",
|
|
25
|
-
"title": "Stale page",
|
|
26
|
-
"lastModifiedDateTime": "2026-05-18T08:00:00Z",
|
|
27
|
-
"content": "<html><body>Out of window</body></html>"
|
|
28
|
-
}
|
|
29
|
-
]
|
|
30
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"section": {
|
|
3
|
-
"id": "015a0ac9-58f4-4f34-98b3-a60075b36627",
|
|
4
|
-
"displayName": "ABN AMRO"
|
|
5
|
-
},
|
|
6
|
-
"resolvedSection": null,
|
|
7
|
-
"pages": [
|
|
8
|
-
{
|
|
9
|
-
"id": "p1",
|
|
10
|
-
"title": "Page with content",
|
|
11
|
-
"lastModifiedDateTime": "2026-05-25T10:00:00Z",
|
|
12
|
-
"content": "<html><body>OK</body></html>"
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"id": "p2",
|
|
16
|
-
"title": "Page body unavailable",
|
|
17
|
-
"lastModifiedDateTime": "2026-05-26T11:00:00Z",
|
|
18
|
-
"content": null
|
|
19
|
-
}
|
|
20
|
-
]
|
|
21
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"itemsBySite": {
|
|
3
|
-
"https://abnamro.sharepoint.com/sites/TeamAlfred": [
|
|
4
|
-
{ "id": "01ABC", "name": "Architecture.pptx", "webUrl": "https://abnamro.sharepoint.com/sites/TeamAlfred/Architecture.pptx", "lastModifiedDateTime": "2026-05-25T11:00:00Z" },
|
|
5
|
-
{ "id": "01DEF", "name": "DiscoveryNotes.docx", "webUrl": "https://abnamro.sharepoint.com/sites/TeamAlfred/DiscoveryNotes.docx", "lastModifiedDateTime": "2026-05-27T16:00:00Z" },
|
|
6
|
-
{ "id": "01GHI", "name": "Stale.xlsx", "webUrl": "https://abnamro.sharepoint.com/sites/TeamAlfred/Stale.xlsx", "lastModifiedDateTime": "2026-05-18T09:00:00Z" }
|
|
7
|
-
],
|
|
8
|
-
"https://contoso-evil.sharepoint.com/sites/X": [
|
|
9
|
-
{ "id": "01ZZZ", "name": "Should never be reached.docx", "webUrl": "https://contoso-evil.sharepoint.com/sites/X/Y.docx", "lastModifiedDateTime": "2026-05-26T10:00:00Z" }
|
|
10
|
-
]
|
|
11
|
-
}
|
|
12
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"messagesByChat": {
|
|
3
|
-
"19:e17bf64ba941442590dae0824cc4784c@thread.v2": [
|
|
4
|
-
{ "id": "1747000000000", "createdDateTime": "2026-05-25T09:15:00Z", "from": { "user": { "displayName": "Alice" } }, "body": { "content": "<p>Status update</p>" } },
|
|
5
|
-
{ "id": "1747100000000", "createdDateTime": "2026-05-26T13:00:00Z", "from": { "user": { "displayName": "Bob" } }, "body": { "content": "<p>Will review the doc</p>" } },
|
|
6
|
-
{ "id": "1747200000000", "createdDateTime": "2026-05-28T08:30:00Z", "from": { "user": { "displayName": "Carol" } }, "body": { "content": "<p>Action items captured</p>" } },
|
|
7
|
-
{ "id": "1746000000000", "createdDateTime": "2026-05-18T10:00:00Z", "from": { "user": { "displayName": "Stale" } }, "body": { "content": "<p>Out of window</p>" } }
|
|
8
|
-
],
|
|
9
|
-
"19:empty@thread.v2": []
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
// plugin/runners/test/integration/pull-email.integration.test.mjs
|
|
2
|
-
|
|
3
|
-
import { test, before, after } from 'node:test';
|
|
4
|
-
import assert from 'node:assert/strict';
|
|
5
|
-
import { promises as fs } from 'node:fs';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import os from 'node:os';
|
|
8
|
-
import { spawnSync } from 'node:child_process';
|
|
9
|
-
import YAML from 'yaml';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
-
|
|
12
|
-
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const REPO_ROOT = path.resolve(HERE, '..', '..', '..', '..');
|
|
14
|
-
const RUNNER = path.join(REPO_ROOT, 'plugin', 'runners', 'pull-email.mjs');
|
|
15
|
-
const FIXTURE = path.join(HERE, '..', 'fixtures', 'email-abn-amro.json');
|
|
16
|
-
|
|
17
|
-
let projectRoot;
|
|
18
|
-
|
|
19
|
-
before(async () => {
|
|
20
|
-
projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-email-int-'));
|
|
21
|
-
await fs.mkdir(path.join(projectRoot, 'Evidence', 'ushak'), { recursive: true });
|
|
22
|
-
await fs.writeFile(path.join(projectRoot, 'integrations.yml'), YAML.stringify({}));
|
|
23
|
-
});
|
|
24
|
-
after(async () => { await fs.rm(projectRoot, { recursive: true, force: true }); });
|
|
25
|
-
|
|
26
|
-
function runRunner(extra = []) {
|
|
27
|
-
return spawnSync(process.execPath, [RUNNER,
|
|
28
|
-
'--project', projectRoot, '--alias', 'ushak',
|
|
29
|
-
'--entity', '23. ABN AMRO',
|
|
30
|
-
'--week', '2026-05-25', '--fixture', FIXTURE,
|
|
31
|
-
...extra,
|
|
32
|
-
], { encoding: 'utf8' });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
test('captures only messages within the ISO week window', () => {
|
|
36
|
-
const res = runRunner();
|
|
37
|
-
assert.equal(res.status, 0, `stderr: ${res.stderr}`);
|
|
38
|
-
const result = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
39
|
-
assert.equal(result.status, 'captured');
|
|
40
|
-
assert.equal(result.items_pulled, 2);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('writes per-week directory under sanitized folder name', async () => {
|
|
44
|
-
const dir = path.join(projectRoot, 'Evidence', 'ushak', 'email', '23. ABN AMRO', '2026-05-25');
|
|
45
|
-
const msgs = YAML.parse(await fs.readFile(path.join(dir, 'messages.yml'), 'utf8'));
|
|
46
|
-
assert.equal(msgs.length, 2);
|
|
47
|
-
const md = await fs.readFile(path.join(dir, 'index.md'), 'utf8');
|
|
48
|
-
assert.match(md, /Kickoff agenda/);
|
|
49
|
-
assert.match(md, /Architecture review notes/);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('ledger entry captured with folder metadata', async () => {
|
|
53
|
-
const ledger = YAML.parse(await fs.readFile(path.join(projectRoot, 'Evidence', 'ushak', '_ledger.yml'), 'utf8'));
|
|
54
|
-
const cell = ledger.entries['email::23. ABN AMRO::2026-05-25'];
|
|
55
|
-
assert.ok(cell);
|
|
56
|
-
assert.equal(cell.last_status, 'captured');
|
|
57
|
-
assert.equal(cell.items_pulled, 2);
|
|
58
|
-
assert.equal(cell.folder_id, 'AAMkAGI=');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('folder not found → status failed (exit 0, recorded in ledger)', async () => {
|
|
62
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
63
|
-
'--project', projectRoot, '--alias', 'ushak',
|
|
64
|
-
'--entity', 'NoSuchFolder', '--week', '2026-05-25', '--fixture', FIXTURE,
|
|
65
|
-
], { encoding: 'utf8' });
|
|
66
|
-
assert.equal(res.status, 0);
|
|
67
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
68
|
-
assert.equal(r.status, 'failed');
|
|
69
|
-
assert.equal(r.errors[0].signature, 'folder-not-found');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('dry-run does not write files', async () => {
|
|
73
|
-
const projectRoot2 = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-email-dry-'));
|
|
74
|
-
await fs.mkdir(path.join(projectRoot2, 'Evidence', 'ushak'), { recursive: true });
|
|
75
|
-
await fs.writeFile(path.join(projectRoot2, 'integrations.yml'), YAML.stringify({}));
|
|
76
|
-
try {
|
|
77
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
78
|
-
'--project', projectRoot2, '--alias', 'ushak',
|
|
79
|
-
'--entity', '23. ABN AMRO', '--week', '2026-05-25', '--fixture', FIXTURE, '--dry-run',
|
|
80
|
-
], { encoding: 'utf8' });
|
|
81
|
-
assert.equal(res.status, 0);
|
|
82
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
83
|
-
assert.deepEqual(r.files_written, []);
|
|
84
|
-
let exists = true;
|
|
85
|
-
try { await fs.access(path.join(projectRoot2, 'Evidence', 'ushak', 'email')); } catch { exists = false; }
|
|
86
|
-
assert.equal(exists, false);
|
|
87
|
-
} finally {
|
|
88
|
-
await fs.rm(projectRoot2, { recursive: true, force: true });
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('missing --entity exits 2', () => {
|
|
93
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
94
|
-
'--project', projectRoot, '--alias', 'ushak', '--fixture', FIXTURE,
|
|
95
|
-
], { encoding: 'utf8' });
|
|
96
|
-
assert.equal(res.status, 2);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('v5.6.0: non-retryable error emits a learning candidate file', async () => {
|
|
100
|
-
const projectRoot3 = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-email-novel-'));
|
|
101
|
-
await fs.mkdir(path.join(projectRoot3, 'Evidence', 'ushak'), { recursive: true });
|
|
102
|
-
await fs.writeFile(path.join(projectRoot3, 'integrations.yml'), YAML.stringify({}));
|
|
103
|
-
const NOVEL_FIXTURE = path.join(HERE, '..', 'fixtures', 'email-novel-error.json');
|
|
104
|
-
try {
|
|
105
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
106
|
-
'--project', projectRoot3, '--alias', 'ushak',
|
|
107
|
-
'--entity', '23. ABN AMRO', '--week', '2026-05-25', '--fixture', NOVEL_FIXTURE,
|
|
108
|
-
], { encoding: 'utf8' });
|
|
109
|
-
assert.equal(res.status, 0, `stderr: ${res.stderr}`);
|
|
110
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
111
|
-
assert.equal(r.status, 'failed');
|
|
112
|
-
const candDir = path.join(projectRoot3, 'Evidence', '_learnings-candidates');
|
|
113
|
-
const entries = await fs.readdir(candDir);
|
|
114
|
-
const mdFiles = entries.filter(f => f.endsWith('.md'));
|
|
115
|
-
assert.equal(mdFiles.length, 1, `expected exactly one candidate, got: ${entries.join(', ')}`);
|
|
116
|
-
const body = await fs.readFile(path.join(candDir, mdFiles[0]), 'utf8');
|
|
117
|
-
assert.match(body, /fetch-failed/);
|
|
118
|
-
assert.match(body, /unexpected null/);
|
|
119
|
-
// Re-run within window — should not duplicate.
|
|
120
|
-
spawnSync(process.execPath, [RUNNER,
|
|
121
|
-
'--project', projectRoot3, '--alias', 'ushak',
|
|
122
|
-
'--entity', '23. ABN AMRO', '--week', '2026-05-25', '--fixture', NOVEL_FIXTURE,
|
|
123
|
-
], { encoding: 'utf8' });
|
|
124
|
-
const entries2 = await fs.readdir(candDir);
|
|
125
|
-
const mdFiles2 = entries2.filter(f => f.endsWith('.md'));
|
|
126
|
-
assert.equal(mdFiles2.length, 1, 'dedup should prevent a 2nd file within 7d');
|
|
127
|
-
} finally {
|
|
128
|
-
await fs.rm(projectRoot3, { recursive: true, force: true });
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test('v5.6.0: user-side error does NOT emit a learning candidate', async () => {
|
|
133
|
-
const projectRoot4 = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-email-userside-'));
|
|
134
|
-
await fs.mkdir(path.join(projectRoot4, 'Evidence', 'ushak'), { recursive: true });
|
|
135
|
-
await fs.writeFile(path.join(projectRoot4, 'integrations.yml'), YAML.stringify({}));
|
|
136
|
-
try {
|
|
137
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
138
|
-
'--project', projectRoot4, '--alias', 'ushak',
|
|
139
|
-
'--entity', 'NoSuchFolder', '--week', '2026-05-25', '--fixture', FIXTURE,
|
|
140
|
-
], { encoding: 'utf8' });
|
|
141
|
-
assert.equal(res.status, 0);
|
|
142
|
-
let exists = true;
|
|
143
|
-
try { await fs.access(path.join(projectRoot4, 'Evidence', '_learnings-candidates')); }
|
|
144
|
-
catch { exists = false; }
|
|
145
|
-
assert.equal(exists, false, 'folder-not-found should never trigger candidate emission');
|
|
146
|
-
} finally {
|
|
147
|
-
await fs.rm(projectRoot4, { recursive: true, force: true });
|
|
148
|
-
}
|
|
149
|
-
});
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
// plugin/runners/test/integration/pull-meetings.integration.test.mjs
|
|
2
|
-
|
|
3
|
-
import { test, before, after } from 'node:test';
|
|
4
|
-
import assert from 'node:assert/strict';
|
|
5
|
-
import { promises as fs } from 'node:fs';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import os from 'node:os';
|
|
8
|
-
import { spawnSync } from 'node:child_process';
|
|
9
|
-
import YAML from 'yaml';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
-
|
|
12
|
-
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const REPO_ROOT = path.resolve(HERE, '..', '..', '..', '..');
|
|
14
|
-
const RUNNER = path.join(REPO_ROOT, 'plugin', 'runners', 'pull-meetings.mjs');
|
|
15
|
-
const FIXTURE_OK = path.join(HERE, '..', 'fixtures', 'meetings-abn-amro.json');
|
|
16
|
-
const FIXTURE_BU = path.join(HERE, '..', 'fixtures', 'meetings-body-unavailable.json');
|
|
17
|
-
const JOIN_URL_OK = 'https://teams.microsoft.com/meet/31827546911768?p=YOgZHuaffeF0MGBCNt';
|
|
18
|
-
const JOIN_URL_BU = 'https://teams.microsoft.com/meet/26457824867211?p=zdSjg6cqHoHPA8Hx11';
|
|
19
|
-
|
|
20
|
-
let projectRoot;
|
|
21
|
-
|
|
22
|
-
before(async () => {
|
|
23
|
-
projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-meet-int-'));
|
|
24
|
-
await fs.mkdir(path.join(projectRoot, 'Evidence', 'ushak'), { recursive: true });
|
|
25
|
-
await fs.writeFile(path.join(projectRoot, 'integrations.yml'), YAML.stringify({}));
|
|
26
|
-
});
|
|
27
|
-
after(async () => { await fs.rm(projectRoot, { recursive: true, force: true }); });
|
|
28
|
-
|
|
29
|
-
test('captures meeting + transcript verbatim', () => {
|
|
30
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
31
|
-
'--project', projectRoot, '--alias', 'ushak',
|
|
32
|
-
'--entity', JOIN_URL_OK, '--week', '2026-05-25', '--fixture', FIXTURE_OK,
|
|
33
|
-
], { encoding: 'utf8' });
|
|
34
|
-
assert.equal(res.status, 0, `stderr: ${res.stderr}`);
|
|
35
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
36
|
-
assert.equal(r.status, 'captured');
|
|
37
|
-
assert.equal(r.items_pulled, 1);
|
|
38
|
-
assert.ok(r.transcript_chars > 0);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('transcript written verbatim to verbatim/transcript.txt', async () => {
|
|
42
|
-
// Find the meetings directory (only one)
|
|
43
|
-
const meetDir = path.join(projectRoot, 'Evidence', 'ushak', 'meetings');
|
|
44
|
-
const entries = await fs.readdir(meetDir);
|
|
45
|
-
assert.equal(entries.length, 1);
|
|
46
|
-
const tx = await fs.readFile(path.join(meetDir, entries[0], 'verbatim', 'transcript.txt'), 'utf8');
|
|
47
|
-
assert.match(tx, /WEBVTT/);
|
|
48
|
-
assert.match(tx, /Architecture diagram is ready for review/);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('body-unavailable → defers (4h marker) and writes meeting.yml only', async () => {
|
|
52
|
-
const projectRoot2 = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-meet-bu-'));
|
|
53
|
-
await fs.mkdir(path.join(projectRoot2, 'Evidence', 'ushak'), { recursive: true });
|
|
54
|
-
await fs.writeFile(path.join(projectRoot2, 'integrations.yml'), YAML.stringify({}));
|
|
55
|
-
try {
|
|
56
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
57
|
-
'--project', projectRoot2, '--alias', 'ushak',
|
|
58
|
-
'--entity', JOIN_URL_BU, '--week', '2026-05-25', '--fixture', FIXTURE_BU,
|
|
59
|
-
], { encoding: 'utf8' });
|
|
60
|
-
assert.equal(res.status, 0, `stderr: ${res.stderr}`);
|
|
61
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
62
|
-
assert.equal(r.status, 'body-unavailable');
|
|
63
|
-
assert.equal(r.errors[0].signature, 'body-unavailable');
|
|
64
|
-
assert.equal(r.errors[0].retry_after_min, 240);
|
|
65
|
-
// deferred-retries directory has an entry
|
|
66
|
-
const deferredDir = path.join(projectRoot2, 'Evidence', 'ushak', '_deferred-retries', 'meetings');
|
|
67
|
-
const files = await fs.readdir(deferredDir);
|
|
68
|
-
assert.ok(files.length >= 1);
|
|
69
|
-
// No transcript file written
|
|
70
|
-
const meetDir = path.join(projectRoot2, 'Evidence', 'ushak', 'meetings');
|
|
71
|
-
const entries = await fs.readdir(meetDir);
|
|
72
|
-
let txExists = true;
|
|
73
|
-
try { await fs.access(path.join(meetDir, entries[0], 'verbatim', 'transcript.txt')); } catch { txExists = false; }
|
|
74
|
-
assert.equal(txExists, false);
|
|
75
|
-
} finally { await fs.rm(projectRoot2, { recursive: true, force: true }); }
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('ledger reflects captured state with transcript_chars', async () => {
|
|
79
|
-
const ledger = YAML.parse(await fs.readFile(path.join(projectRoot, 'Evidence', 'ushak', '_ledger.yml'), 'utf8'));
|
|
80
|
-
const cell = ledger.entries[`meetings::${JOIN_URL_OK}::2026-05-25`];
|
|
81
|
-
assert.ok(cell);
|
|
82
|
-
assert.equal(cell.last_status, 'captured');
|
|
83
|
-
assert.equal(cell.transcript_available, true);
|
|
84
|
-
assert.ok(cell.transcript_chars > 0);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('missing --entity exits 2', () => {
|
|
88
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
89
|
-
'--project', projectRoot, '--alias', 'ushak', '--fixture', FIXTURE_OK,
|
|
90
|
-
], { encoding: 'utf8' });
|
|
91
|
-
assert.equal(res.status, 2);
|
|
92
|
-
});
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
// plugin/runners/test/integration/pull-onenote.integration.test.mjs
|
|
2
|
-
|
|
3
|
-
import { test, before, after } from 'node:test';
|
|
4
|
-
import assert from 'node:assert/strict';
|
|
5
|
-
import { promises as fs } from 'node:fs';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import os from 'node:os';
|
|
8
|
-
import { spawnSync } from 'node:child_process';
|
|
9
|
-
import YAML from 'yaml';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
-
|
|
12
|
-
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const REPO_ROOT = path.resolve(HERE, '..', '..', '..', '..');
|
|
14
|
-
const RUNNER = path.join(REPO_ROOT, 'plugin', 'runners', 'pull-onenote.mjs');
|
|
15
|
-
const FIXTURE_OK = path.join(HERE, '..', 'fixtures', 'onenote-abn-amro.json');
|
|
16
|
-
const FIXTURE_PARTIAL = path.join(HERE, '..', 'fixtures', 'onenote-partial.json');
|
|
17
|
-
const SECTION_ID = '015a0ac9-58f4-4f34-98b3-a60075b36627';
|
|
18
|
-
|
|
19
|
-
let projectRoot;
|
|
20
|
-
|
|
21
|
-
before(async () => {
|
|
22
|
-
projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-on-int-'));
|
|
23
|
-
await fs.mkdir(path.join(projectRoot, 'Evidence', 'ushak'), { recursive: true });
|
|
24
|
-
await fs.writeFile(path.join(projectRoot, 'integrations.yml'), YAML.stringify({}));
|
|
25
|
-
});
|
|
26
|
-
after(async () => { await fs.rm(projectRoot, { recursive: true, force: true }); });
|
|
27
|
-
|
|
28
|
-
test('captures all pages within the week, writes one HTML file per page', () => {
|
|
29
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
30
|
-
'--project', projectRoot, '--alias', 'ushak',
|
|
31
|
-
'--entity', SECTION_ID, '--week', '2026-05-25', '--fixture', FIXTURE_OK,
|
|
32
|
-
], { encoding: 'utf8' });
|
|
33
|
-
assert.equal(res.status, 0, `stderr: ${res.stderr}`);
|
|
34
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
35
|
-
assert.equal(r.status, 'captured');
|
|
36
|
-
assert.equal(r.pages_enumerated, 2); // p1 + p2 (p3 is stale)
|
|
37
|
-
assert.equal(r.items_pulled, 2);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('per-page HTML written verbatim under content/<id>.html', async () => {
|
|
41
|
-
const dir = path.join(projectRoot, 'Evidence', 'ushak', 'onenote', SECTION_ID, '2026-05-25', 'content');
|
|
42
|
-
const p1 = await fs.readFile(path.join(dir, 'p1.html'), 'utf8');
|
|
43
|
-
const p2 = await fs.readFile(path.join(dir, 'p2.html'), 'utf8');
|
|
44
|
-
assert.match(p1, /Kickoff/);
|
|
45
|
-
assert.match(p2, /Three-tier/);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('partial: body-unavailable pages NOT enqueued in deferred-retries', async () => {
|
|
49
|
-
const projectRoot2 = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-on-part-'));
|
|
50
|
-
await fs.mkdir(path.join(projectRoot2, 'Evidence', 'ushak'), { recursive: true });
|
|
51
|
-
await fs.writeFile(path.join(projectRoot2, 'integrations.yml'), YAML.stringify({}));
|
|
52
|
-
try {
|
|
53
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
54
|
-
'--project', projectRoot2, '--alias', 'ushak',
|
|
55
|
-
'--entity', SECTION_ID, '--week', '2026-05-25', '--fixture', FIXTURE_PARTIAL,
|
|
56
|
-
], { encoding: 'utf8' });
|
|
57
|
-
assert.equal(res.status, 0, `stderr: ${res.stderr}`);
|
|
58
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
59
|
-
assert.equal(r.status, 'partial');
|
|
60
|
-
assert.equal(r.items_pulled, 1);
|
|
61
|
-
assert.deepEqual(r.body_unavailable, ['p2']);
|
|
62
|
-
// Confirm OneNote is forbidden from enqueueing body-unavailable (lib/deferred.mjs throws)
|
|
63
|
-
let deferredCount = 0;
|
|
64
|
-
try {
|
|
65
|
-
const files = await fs.readdir(path.join(projectRoot2, 'Evidence', 'ushak', '_deferred-retries', 'onenote'));
|
|
66
|
-
deferredCount = files.length;
|
|
67
|
-
} catch { deferredCount = 0; }
|
|
68
|
-
assert.equal(deferredCount, 0);
|
|
69
|
-
} finally { await fs.rm(projectRoot2, { recursive: true, force: true }); }
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('ledger captures pages_enumerated and section_file_id', async () => {
|
|
73
|
-
const ledger = YAML.parse(await fs.readFile(path.join(projectRoot, 'Evidence', 'ushak', '_ledger.yml'), 'utf8'));
|
|
74
|
-
const cell = ledger.entries[`onenote::${SECTION_ID}::2026-05-25`];
|
|
75
|
-
assert.ok(cell);
|
|
76
|
-
assert.equal(cell.last_status, 'captured');
|
|
77
|
-
assert.equal(cell.pages_enumerated, 2);
|
|
78
|
-
assert.equal(cell.section_file_id, SECTION_ID);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('missing --entity exits 2', () => {
|
|
82
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
83
|
-
'--project', projectRoot, '--alias', 'ushak', '--fixture', FIXTURE_OK,
|
|
84
|
-
], { encoding: 'utf8' });
|
|
85
|
-
assert.equal(res.status, 2);
|
|
86
|
-
});
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// plugin/runners/test/integration/pull-sharepoint.integration.test.mjs
|
|
2
|
-
|
|
3
|
-
import { test, before, after } from 'node:test';
|
|
4
|
-
import assert from 'node:assert/strict';
|
|
5
|
-
import { promises as fs } from 'node:fs';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import os from 'node:os';
|
|
8
|
-
import { spawnSync } from 'node:child_process';
|
|
9
|
-
import YAML from 'yaml';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
-
|
|
12
|
-
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const REPO_ROOT = path.resolve(HERE, '..', '..', '..', '..');
|
|
14
|
-
const RUNNER = path.join(REPO_ROOT, 'plugin', 'runners', 'pull-sharepoint.mjs');
|
|
15
|
-
const FIXTURE = path.join(HERE, '..', 'fixtures', 'sharepoint-abn-amro.json');
|
|
16
|
-
const SITE_OK = 'https://abnamro.sharepoint.com/sites/TeamAlfred';
|
|
17
|
-
const SITE_BAD = 'https://contoso-evil.sharepoint.com/sites/X';
|
|
18
|
-
|
|
19
|
-
let projectRoot;
|
|
20
|
-
|
|
21
|
-
before(async () => {
|
|
22
|
-
projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-sp-int-'));
|
|
23
|
-
await fs.mkdir(path.join(projectRoot, 'Evidence', 'ushak'), { recursive: true });
|
|
24
|
-
await fs.writeFile(path.join(projectRoot, 'integrations.yml'), YAML.stringify({}));
|
|
25
|
-
});
|
|
26
|
-
after(async () => { await fs.rm(projectRoot, { recursive: true, force: true }); });
|
|
27
|
-
|
|
28
|
-
test('captures items within ISO week — no bundling (one file per item)', async () => {
|
|
29
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
30
|
-
'--project', projectRoot, '--alias', 'ushak',
|
|
31
|
-
'--entity', SITE_OK, '--week', '2026-05-25',
|
|
32
|
-
'--allowed-tenants', 'abnamro.sharepoint.com',
|
|
33
|
-
'--fixture', FIXTURE,
|
|
34
|
-
], { encoding: 'utf8' });
|
|
35
|
-
assert.equal(res.status, 0, `stderr: ${res.stderr}`);
|
|
36
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
37
|
-
assert.equal(r.status, 'captured');
|
|
38
|
-
assert.equal(r.items_pulled, 2);
|
|
39
|
-
// No bundle file like items.yml — only one-file-per-item under items/
|
|
40
|
-
const dir = path.join(projectRoot, 'Evidence', '_shared', 'sharepoint', r.site_hash, '2026-05-25');
|
|
41
|
-
const itemFiles = await fs.readdir(path.join(dir, 'items'));
|
|
42
|
-
assert.equal(itemFiles.length, 2);
|
|
43
|
-
for (const f of itemFiles) assert.ok(f.endsWith('.yml'));
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('cross-tenant site blocked → status failed', async () => {
|
|
47
|
-
const projectRoot2 = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-sp-xtenant-'));
|
|
48
|
-
await fs.mkdir(path.join(projectRoot2, 'Evidence', 'ushak'), { recursive: true });
|
|
49
|
-
await fs.writeFile(path.join(projectRoot2, 'integrations.yml'), YAML.stringify({}));
|
|
50
|
-
try {
|
|
51
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
52
|
-
'--project', projectRoot2, '--alias', 'ushak',
|
|
53
|
-
'--entity', SITE_BAD, '--week', '2026-05-25',
|
|
54
|
-
'--allowed-tenants', 'abnamro.sharepoint.com,microsoft.sharepoint.com',
|
|
55
|
-
'--fixture', FIXTURE,
|
|
56
|
-
], { encoding: 'utf8' });
|
|
57
|
-
assert.equal(res.status, 0);
|
|
58
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
59
|
-
assert.equal(r.status, 'failed');
|
|
60
|
-
assert.equal(r.errors[0].signature, 'cross-tenant-blocked');
|
|
61
|
-
// No items written
|
|
62
|
-
let exists = true;
|
|
63
|
-
try { await fs.access(path.join(projectRoot2, 'Evidence', '_shared', 'sharepoint')); } catch { exists = false; }
|
|
64
|
-
assert.equal(exists, false);
|
|
65
|
-
} finally { await fs.rm(projectRoot2, { recursive: true, force: true }); }
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('item files have correct content', async () => {
|
|
69
|
-
const r = JSON.parse(spawnSync(process.execPath, [RUNNER,
|
|
70
|
-
'--project', projectRoot, '--alias', 'ushak',
|
|
71
|
-
'--entity', SITE_OK, '--week', '2026-05-25',
|
|
72
|
-
'--allowed-tenants', 'abnamro.sharepoint.com',
|
|
73
|
-
'--fixture', FIXTURE,
|
|
74
|
-
], { encoding: 'utf8' }).stdout.trim().split('\n').pop());
|
|
75
|
-
const dir = path.join(projectRoot, 'Evidence', '_shared', 'sharepoint', r.site_hash, '2026-05-25', 'items');
|
|
76
|
-
const it = YAML.parse(await fs.readFile(path.join(dir, '01ABC.yml'), 'utf8'));
|
|
77
|
-
assert.equal(it.name, 'Architecture.pptx');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('ledger entry uses raw site url as cell key', async () => {
|
|
81
|
-
const ledger = YAML.parse(await fs.readFile(path.join(projectRoot, 'Evidence', 'ushak', '_ledger.yml'), 'utf8'));
|
|
82
|
-
const cell = ledger.entries[`sharepoint::${SITE_OK}::2026-05-25`];
|
|
83
|
-
assert.ok(cell);
|
|
84
|
-
assert.equal(cell.last_status, 'captured');
|
|
85
|
-
assert.equal(cell.items_pulled, 2);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('missing --entity exits 2', () => {
|
|
89
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
90
|
-
'--project', projectRoot, '--alias', 'ushak', '--fixture', FIXTURE,
|
|
91
|
-
], { encoding: 'utf8' });
|
|
92
|
-
assert.equal(res.status, 2);
|
|
93
|
-
});
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
// plugin/runners/test/integration/pull-teams.integration.test.mjs
|
|
2
|
-
|
|
3
|
-
import { test, before, after } from 'node:test';
|
|
4
|
-
import assert from 'node:assert/strict';
|
|
5
|
-
import { promises as fs } from 'node:fs';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import os from 'node:os';
|
|
8
|
-
import { spawnSync } from 'node:child_process';
|
|
9
|
-
import YAML from 'yaml';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
-
|
|
12
|
-
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const REPO_ROOT = path.resolve(HERE, '..', '..', '..', '..');
|
|
14
|
-
const RUNNER = path.join(REPO_ROOT, 'plugin', 'runners', 'pull-teams.mjs');
|
|
15
|
-
const FIXTURE = path.join(HERE, '..', 'fixtures', 'teams-abn-amro.json');
|
|
16
|
-
const CHAT_ID = '19:e17bf64ba941442590dae0824cc4784c@thread.v2';
|
|
17
|
-
|
|
18
|
-
let projectRoot;
|
|
19
|
-
|
|
20
|
-
before(async () => {
|
|
21
|
-
projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-teams-int-'));
|
|
22
|
-
await fs.mkdir(path.join(projectRoot, 'Evidence', 'ushak'), { recursive: true });
|
|
23
|
-
await fs.writeFile(path.join(projectRoot, 'integrations.yml'), YAML.stringify({}));
|
|
24
|
-
});
|
|
25
|
-
after(async () => { await fs.rm(projectRoot, { recursive: true, force: true }); });
|
|
26
|
-
|
|
27
|
-
function runRunner(entity, extra = []) {
|
|
28
|
-
return spawnSync(process.execPath, [RUNNER,
|
|
29
|
-
'--project', projectRoot, '--alias', 'ushak',
|
|
30
|
-
'--entity', entity,
|
|
31
|
-
'--week', '2026-05-25', '--fixture', FIXTURE,
|
|
32
|
-
...extra,
|
|
33
|
-
], { encoding: 'utf8' });
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
test('captures only messages within the ISO week window', () => {
|
|
37
|
-
const res = runRunner(CHAT_ID);
|
|
38
|
-
assert.equal(res.status, 0, `stderr: ${res.stderr}`);
|
|
39
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
40
|
-
assert.equal(r.status, 'captured');
|
|
41
|
-
assert.equal(r.items_pulled, 3);
|
|
42
|
-
assert.ok(r.chat_hash && r.chat_hash.length === 12);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('uses shortHash for directory (unsafe chat id chars sanitized)', async () => {
|
|
46
|
-
const r = JSON.parse(runRunner(CHAT_ID).stdout.trim().split('\n').pop());
|
|
47
|
-
const dir = path.join(projectRoot, 'Evidence', 'ushak', 'teams', r.chat_hash, '2026-05-25');
|
|
48
|
-
const msgs = YAML.parse(await fs.readFile(path.join(dir, 'messages.yml'), 'utf8'));
|
|
49
|
-
assert.equal(msgs.length, 3);
|
|
50
|
-
const chat = YAML.parse(await fs.readFile(path.join(dir, 'chat.yml'), 'utf8'));
|
|
51
|
-
assert.equal(chat.chat_id, CHAT_ID);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('ledger entry retains raw chat id as cell key', async () => {
|
|
55
|
-
const ledger = YAML.parse(await fs.readFile(path.join(projectRoot, 'Evidence', 'ushak', '_ledger.yml'), 'utf8'));
|
|
56
|
-
const cell = ledger.entries[`teams::${CHAT_ID}::2026-05-25`];
|
|
57
|
-
assert.ok(cell);
|
|
58
|
-
assert.equal(cell.last_status, 'captured');
|
|
59
|
-
assert.equal(cell.items_pulled, 3);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('empty chat returns no-activity', () => {
|
|
63
|
-
const res = runRunner('19:empty@thread.v2');
|
|
64
|
-
assert.equal(res.status, 0);
|
|
65
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
66
|
-
assert.equal(r.status, 'no-activity');
|
|
67
|
-
assert.equal(r.items_pulled, 0);
|
|
68
|
-
assert.deepEqual(r.files_written, []);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('index.md preview strips HTML', async () => {
|
|
72
|
-
const r = JSON.parse(runRunner(CHAT_ID).stdout.trim().split('\n').pop());
|
|
73
|
-
const md = await fs.readFile(path.join(projectRoot, 'Evidence', 'ushak', 'teams', r.chat_hash, '2026-05-25', 'index.md'), 'utf8');
|
|
74
|
-
assert.match(md, /Status update/);
|
|
75
|
-
assert.doesNotMatch(md, /<p>/);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('dry-run writes nothing', async () => {
|
|
79
|
-
const projectRoot2 = await fs.mkdtemp(path.join(os.tmpdir(), 'kushi-teams-dry-'));
|
|
80
|
-
await fs.mkdir(path.join(projectRoot2, 'Evidence', 'ushak'), { recursive: true });
|
|
81
|
-
await fs.writeFile(path.join(projectRoot2, 'integrations.yml'), YAML.stringify({}));
|
|
82
|
-
try {
|
|
83
|
-
const res = spawnSync(process.execPath, [RUNNER,
|
|
84
|
-
'--project', projectRoot2, '--alias', 'ushak',
|
|
85
|
-
'--entity', CHAT_ID, '--week', '2026-05-25', '--fixture', FIXTURE, '--dry-run',
|
|
86
|
-
], { encoding: 'utf8' });
|
|
87
|
-
assert.equal(res.status, 0);
|
|
88
|
-
const r = JSON.parse(res.stdout.trim().split('\n').pop());
|
|
89
|
-
assert.deepEqual(r.files_written, []);
|
|
90
|
-
} finally { await fs.rm(projectRoot2, { recursive: true, force: true }); }
|
|
91
|
-
});
|