@clawos-dev/clawd 0.2.50 → 0.2.51-beta.78.2024c11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/persona-defaults/persona-clawd-helper/CLAUDE.md +1 -1
- package/dist/persona-defaults/persona-knowledge-base/CLAUDE.md +19 -0
- package/dist/persona-defaults/persona-researcher/CLAUDE.md +20 -1
- package/package.json +1 -1
- package/dist/persona-defaults/persona-knowledge-base/.claude/skills/karpathy-llm-wiki/SKILL.md +0 -187
- package/dist/persona-defaults/persona-knowledge-base/.claude/skills/karpathy-llm-wiki/references/archive-template.md +0 -21
- package/dist/persona-defaults/persona-knowledge-base/.claude/skills/karpathy-llm-wiki/references/article-template.md +0 -20
- package/dist/persona-defaults/persona-knowledge-base/.claude/skills/karpathy-llm-wiki/references/index-template.md +0 -18
- package/dist/persona-defaults/persona-knowledge-base/.claude/skills/karpathy-llm-wiki/references/raw-template.md +0 -7
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/README.md +0 -119
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/SKILL.md +0 -108
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/reference/continuation.md +0 -167
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/reference/html-generation.md +0 -103
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/reference/methodology.md +0 -421
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/reference/quality-gates.md +0 -192
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/reference/report-assembly.md +0 -130
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/reference/weasyprint_guidelines.md +0 -324
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/requirements.txt +0 -14
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/schemas/claim.schema.json +0 -49
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/schemas/evidence.schema.json +0 -43
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/schemas/run_manifest.schema.json +0 -97
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/schemas/source.schema.json +0 -49
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/scripts/citation_manager.py +0 -300
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/scripts/evidence_store.py +0 -205
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/scripts/extract_claims.py +0 -358
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/scripts/md_to_html.py +0 -330
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/scripts/research_engine.py +0 -584
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/scripts/source_evaluator.py +0 -292
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/scripts/validate_report.py +0 -354
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/scripts/verify_citations.py +0 -426
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/scripts/verify_claim_support.py +0 -344
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/scripts/verify_html.py +0 -220
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/templates/mckinsey_report_template.html +0 -443
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/templates/report_template.md +0 -414
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/tests/fixtures/invalid_report.md +0 -27
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/tests/fixtures/valid_report.md +0 -114
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/tests/test_citation_manager.py +0 -195
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/tests/test_evidence_store.py +0 -166
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/tests/test_extract_claims.py +0 -213
- package/dist/persona-defaults/persona-researcher/.claude/skills/deep-research/tests/test_verify_claim_support.py +0 -230
- package/dist/persona-defaults/persona-researcher/skills-lock.json +0 -11
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Smoke tests for citation_manager.py CLI."""
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
|
-
import subprocess
|
|
7
|
-
import sys
|
|
8
|
-
import tempfile
|
|
9
|
-
import unittest
|
|
10
|
-
|
|
11
|
-
SCRIPT = os.path.join(os.path.dirname(__file__), '..', 'scripts', 'citation_manager.py')
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def run_cm(*args: str) -> dict:
|
|
15
|
-
"""Run citation_manager.py with args, return parsed JSON from stdout."""
|
|
16
|
-
result = subprocess.run(
|
|
17
|
-
[sys.executable, SCRIPT, *args],
|
|
18
|
-
capture_output=True, text=True,
|
|
19
|
-
)
|
|
20
|
-
if result.returncode != 0:
|
|
21
|
-
raise RuntimeError(f'Exit {result.returncode}: {result.stderr}')
|
|
22
|
-
return json.loads(result.stdout) if result.stdout.strip().startswith(('{', '[')) else result.stdout
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class TestInitRun(unittest.TestCase):
|
|
26
|
-
def test_creates_manifest_and_artifacts(self):
|
|
27
|
-
with tempfile.TemporaryDirectory() as d:
|
|
28
|
-
out = run_cm('init-run', '--out-dir', d, '--query', 'test question', '--mode', 'deep')
|
|
29
|
-
self.assertEqual(out['status'], 'ok')
|
|
30
|
-
|
|
31
|
-
# Manifest exists and has correct fields
|
|
32
|
-
manifest = json.load(open(os.path.join(d, 'run_manifest.json')))
|
|
33
|
-
self.assertEqual(manifest['version'], '3.0.0')
|
|
34
|
-
self.assertEqual(manifest['query'], 'test question')
|
|
35
|
-
self.assertEqual(manifest['mode'], 'deep')
|
|
36
|
-
self.assertIsNotNone(manifest['started_at'])
|
|
37
|
-
self.assertIsNone(manifest['finished_at'])
|
|
38
|
-
self.assertEqual(manifest['artifact_paths']['sources'], 'sources.jsonl')
|
|
39
|
-
|
|
40
|
-
# Empty JSONL files exist
|
|
41
|
-
for name in ('sources.jsonl', 'evidence.jsonl', 'claims.jsonl'):
|
|
42
|
-
path = os.path.join(d, name)
|
|
43
|
-
self.assertTrue(os.path.exists(path), f'{name} missing')
|
|
44
|
-
self.assertEqual(os.path.getsize(path), 0)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class TestRegisterSource(unittest.TestCase):
|
|
48
|
-
def setUp(self):
|
|
49
|
-
self.tmpdir = tempfile.mkdtemp()
|
|
50
|
-
run_cm('init-run', '--out-dir', self.tmpdir, '--query', 'test')
|
|
51
|
-
|
|
52
|
-
def tearDown(self):
|
|
53
|
-
import shutil
|
|
54
|
-
shutil.rmtree(self.tmpdir, ignore_errors=True)
|
|
55
|
-
|
|
56
|
-
def test_register_and_dedup(self):
|
|
57
|
-
src = json.dumps({
|
|
58
|
-
'raw_url': 'https://arxiv.org/abs/2305.14251',
|
|
59
|
-
'title': 'FActScore',
|
|
60
|
-
'source_type': 'academic',
|
|
61
|
-
'year': '2023',
|
|
62
|
-
})
|
|
63
|
-
out1 = run_cm('register-source', '--json', src, '--dir', self.tmpdir)
|
|
64
|
-
self.assertEqual(out1['status'], 'registered')
|
|
65
|
-
self.assertEqual(len(out1['source_id']), 16)
|
|
66
|
-
self.assertTrue(out1['canonical_locator'].startswith('arxiv:'))
|
|
67
|
-
|
|
68
|
-
# Same URL -> duplicate
|
|
69
|
-
out2 = run_cm('register-source', '--json', src, '--dir', self.tmpdir)
|
|
70
|
-
self.assertEqual(out2['status'], 'duplicate')
|
|
71
|
-
self.assertEqual(out2['source_id'], out1['source_id'])
|
|
72
|
-
|
|
73
|
-
def test_doi_canonicalization(self):
|
|
74
|
-
src = json.dumps({
|
|
75
|
-
'raw_url': 'https://doi.org/10.1038/s41586-023-06745-9',
|
|
76
|
-
'title': 'Some Nature paper',
|
|
77
|
-
})
|
|
78
|
-
out = run_cm('register-source', '--json', src, '--dir', self.tmpdir)
|
|
79
|
-
self.assertTrue(out['canonical_locator'].startswith('doi:10.1038/'))
|
|
80
|
-
|
|
81
|
-
def test_url_normalization(self):
|
|
82
|
-
src1 = json.dumps({
|
|
83
|
-
'raw_url': 'https://Example.Com/article?utm_source=google&id=42',
|
|
84
|
-
'title': 'Test',
|
|
85
|
-
})
|
|
86
|
-
src2 = json.dumps({
|
|
87
|
-
'raw_url': 'https://example.com/article?id=42&utm_medium=email',
|
|
88
|
-
'title': 'Test duplicate',
|
|
89
|
-
})
|
|
90
|
-
out1 = run_cm('register-source', '--json', src1, '--dir', self.tmpdir)
|
|
91
|
-
out2 = run_cm('register-source', '--json', src2, '--dir', self.tmpdir)
|
|
92
|
-
# Both should resolve to same canonical locator -> same source_id
|
|
93
|
-
self.assertEqual(out1['source_id'], out2['source_id'])
|
|
94
|
-
self.assertEqual(out2['status'], 'duplicate')
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
class TestAssignDisplayNumbers(unittest.TestCase):
|
|
98
|
-
def test_assigns_in_order(self):
|
|
99
|
-
with tempfile.TemporaryDirectory() as d:
|
|
100
|
-
run_cm('init-run', '--out-dir', d, '--query', 'test')
|
|
101
|
-
|
|
102
|
-
for i, url in enumerate(['https://a.com/1', 'https://b.com/2', 'https://c.com/3']):
|
|
103
|
-
run_cm('register-source', '--json', json.dumps({
|
|
104
|
-
'raw_url': url, 'title': f'Source {i+1}',
|
|
105
|
-
}), '--dir', d)
|
|
106
|
-
|
|
107
|
-
mapping = run_cm('assign-display-numbers', '--dir', d)
|
|
108
|
-
self.assertEqual(len(mapping), 3)
|
|
109
|
-
# Values should be 1, 2, 3
|
|
110
|
-
self.assertEqual(sorted(mapping.values()), [1, 2, 3])
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
class TestExportBibliography(unittest.TestCase):
|
|
114
|
-
def test_markdown_export(self):
|
|
115
|
-
with tempfile.TemporaryDirectory() as d:
|
|
116
|
-
run_cm('init-run', '--out-dir', d, '--query', 'test')
|
|
117
|
-
run_cm('register-source', '--json', json.dumps({
|
|
118
|
-
'raw_url': 'https://arxiv.org/abs/2305.14251',
|
|
119
|
-
'title': 'FActScore',
|
|
120
|
-
'authors': ['Min, S.', 'Krishna, K.'],
|
|
121
|
-
'year': '2023',
|
|
122
|
-
'source_type': 'academic',
|
|
123
|
-
}), '--dir', d)
|
|
124
|
-
|
|
125
|
-
out = run_cm('export-bibliography', '--dir', d, '--style', 'markdown')
|
|
126
|
-
self.assertIn('[1]', out)
|
|
127
|
-
self.assertIn('FActScore', out)
|
|
128
|
-
self.assertIn('Min, S. & Krishna, K.', out)
|
|
129
|
-
|
|
130
|
-
def test_json_export(self):
|
|
131
|
-
with tempfile.TemporaryDirectory() as d:
|
|
132
|
-
run_cm('init-run', '--out-dir', d, '--query', 'test')
|
|
133
|
-
run_cm('register-source', '--json', json.dumps({
|
|
134
|
-
'raw_url': 'https://example.com/paper',
|
|
135
|
-
'title': 'Test Paper',
|
|
136
|
-
}), '--dir', d)
|
|
137
|
-
|
|
138
|
-
out = run_cm('export-bibliography', '--dir', d, '--style', 'json')
|
|
139
|
-
self.assertEqual(len(out), 1)
|
|
140
|
-
self.assertEqual(out[0]['display_number'], 1)
|
|
141
|
-
self.assertEqual(out[0]['title'], 'Test Paper')
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
class TestCanonicalization(unittest.TestCase):
|
|
145
|
-
"""Unit tests for canonicalize_locator without running the CLI."""
|
|
146
|
-
|
|
147
|
-
@classmethod
|
|
148
|
-
def setUpClass(cls):
|
|
149
|
-
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
|
|
150
|
-
from citation_manager import canonicalize_locator, compute_source_id
|
|
151
|
-
cls.canonicalize = staticmethod(canonicalize_locator)
|
|
152
|
-
cls.compute_id = staticmethod(compute_source_id)
|
|
153
|
-
|
|
154
|
-
def test_doi_from_url(self):
|
|
155
|
-
canonicalize_locator = self.canonicalize
|
|
156
|
-
self.assertEqual(
|
|
157
|
-
canonicalize_locator('https://doi.org/10.1038/s41586-023-06745-9'),
|
|
158
|
-
'doi:10.1038/s41586-023-06745-9',
|
|
159
|
-
)
|
|
160
|
-
self.assertEqual(
|
|
161
|
-
canonicalize_locator('https://dx.doi.org/10.1234/test.'),
|
|
162
|
-
'doi:10.1234/test',
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
def test_arxiv_from_url(self):
|
|
166
|
-
canonicalize_locator = self.canonicalize
|
|
167
|
-
self.assertEqual(
|
|
168
|
-
canonicalize_locator('https://arxiv.org/abs/2305.14251v2'),
|
|
169
|
-
'arxiv:2305.14251v2',
|
|
170
|
-
)
|
|
171
|
-
self.assertEqual(
|
|
172
|
-
canonicalize_locator('arxiv:2401.15884'),
|
|
173
|
-
'arxiv:2401.15884',
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
def test_url_strips_tracking(self):
|
|
177
|
-
canonicalize_locator = self.canonicalize
|
|
178
|
-
result = canonicalize_locator('https://Example.Com/page?utm_source=x&key=val')
|
|
179
|
-
self.assertNotIn('utm_source', result)
|
|
180
|
-
self.assertIn('key=val', result)
|
|
181
|
-
self.assertTrue(result.startswith('https://example.com'))
|
|
182
|
-
|
|
183
|
-
def test_url_strips_fragment(self):
|
|
184
|
-
canonicalize_locator = self.canonicalize
|
|
185
|
-
result = canonicalize_locator('https://example.com/page#section')
|
|
186
|
-
self.assertNotIn('#section', result)
|
|
187
|
-
|
|
188
|
-
def test_url_strips_trailing_slash(self):
|
|
189
|
-
canonicalize_locator = self.canonicalize
|
|
190
|
-
result = canonicalize_locator('https://example.com/page/')
|
|
191
|
-
self.assertFalse(result.endswith('/'))
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if __name__ == '__main__':
|
|
195
|
-
unittest.main()
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Smoke tests for evidence_store.py CLI."""
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
|
-
import shutil
|
|
7
|
-
import subprocess
|
|
8
|
-
import sys
|
|
9
|
-
import tempfile
|
|
10
|
-
import unittest
|
|
11
|
-
|
|
12
|
-
SCRIPT = os.path.join(os.path.dirname(__file__), '..', 'scripts', 'evidence_store.py')
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def run_es(*args: str) -> dict | list:
|
|
16
|
-
"""Run evidence_store.py with args, return parsed JSON from stdout."""
|
|
17
|
-
result = subprocess.run(
|
|
18
|
-
[sys.executable, SCRIPT, *args],
|
|
19
|
-
capture_output=True, text=True,
|
|
20
|
-
)
|
|
21
|
-
if result.returncode != 0:
|
|
22
|
-
raise RuntimeError(f'Exit {result.returncode}: {result.stderr}')
|
|
23
|
-
return json.loads(result.stdout)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class TestInit(unittest.TestCase):
|
|
27
|
-
def test_creates_empty_file(self):
|
|
28
|
-
with tempfile.TemporaryDirectory() as d:
|
|
29
|
-
out = run_es('init', '--dir', d)
|
|
30
|
-
self.assertEqual(out['status'], 'ok')
|
|
31
|
-
path = os.path.join(d, 'evidence.jsonl')
|
|
32
|
-
self.assertTrue(os.path.exists(path))
|
|
33
|
-
self.assertEqual(os.path.getsize(path), 0)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class TestAddEvidence(unittest.TestCase):
|
|
37
|
-
def setUp(self):
|
|
38
|
-
self.tmpdir = tempfile.mkdtemp()
|
|
39
|
-
run_es('init', '--dir', self.tmpdir)
|
|
40
|
-
|
|
41
|
-
def tearDown(self):
|
|
42
|
-
shutil.rmtree(self.tmpdir, ignore_errors=True)
|
|
43
|
-
|
|
44
|
-
def test_add_and_dedup(self):
|
|
45
|
-
ev = json.dumps({
|
|
46
|
-
'source_id': 'abcdef0123456789',
|
|
47
|
-
'quote': 'FActScore decomposes generation into atomic facts.',
|
|
48
|
-
'evidence_type': 'direct_quote',
|
|
49
|
-
'locator': 'page 3',
|
|
50
|
-
'retrieval_query': 'factuality evaluation methods',
|
|
51
|
-
})
|
|
52
|
-
out1 = run_es('add', '--json', ev, '--dir', self.tmpdir)
|
|
53
|
-
self.assertEqual(out1['status'], 'added')
|
|
54
|
-
self.assertEqual(len(out1['evidence_id']), 16)
|
|
55
|
-
|
|
56
|
-
# Same quote -> duplicate
|
|
57
|
-
out2 = run_es('add', '--json', ev, '--dir', self.tmpdir)
|
|
58
|
-
self.assertEqual(out2['status'], 'duplicate')
|
|
59
|
-
self.assertEqual(out2['evidence_id'], out1['evidence_id'])
|
|
60
|
-
|
|
61
|
-
def test_whitespace_normalization(self):
|
|
62
|
-
ev1 = json.dumps({
|
|
63
|
-
'source_id': 'abcdef0123456789',
|
|
64
|
-
'quote': ' FActScore decomposes generation into atomic facts. ',
|
|
65
|
-
'evidence_type': 'direct_quote',
|
|
66
|
-
})
|
|
67
|
-
ev2 = json.dumps({
|
|
68
|
-
'source_id': 'abcdef0123456789',
|
|
69
|
-
'quote': 'FActScore decomposes generation into atomic facts.',
|
|
70
|
-
'evidence_type': 'direct_quote',
|
|
71
|
-
})
|
|
72
|
-
out1 = run_es('add', '--json', ev1, '--dir', self.tmpdir)
|
|
73
|
-
out2 = run_es('add', '--json', ev2, '--dir', self.tmpdir)
|
|
74
|
-
# Should be same ID due to normalization
|
|
75
|
-
self.assertEqual(out1['evidence_id'], out2['evidence_id'])
|
|
76
|
-
self.assertEqual(out2['status'], 'duplicate')
|
|
77
|
-
|
|
78
|
-
def test_different_sources_different_ids(self):
|
|
79
|
-
ev1 = json.dumps({
|
|
80
|
-
'source_id': 'aaaaaaaaaaaaaaaa',
|
|
81
|
-
'quote': 'Same quote text.',
|
|
82
|
-
'evidence_type': 'paraphrase',
|
|
83
|
-
})
|
|
84
|
-
ev2 = json.dumps({
|
|
85
|
-
'source_id': 'bbbbbbbbbbbbbbbb',
|
|
86
|
-
'quote': 'Same quote text.',
|
|
87
|
-
'evidence_type': 'paraphrase',
|
|
88
|
-
})
|
|
89
|
-
out1 = run_es('add', '--json', ev1, '--dir', self.tmpdir)
|
|
90
|
-
out2 = run_es('add', '--json', ev2, '--dir', self.tmpdir)
|
|
91
|
-
self.assertNotEqual(out1['evidence_id'], out2['evidence_id'])
|
|
92
|
-
self.assertEqual(out2['status'], 'added')
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class TestListAndExport(unittest.TestCase):
|
|
96
|
-
def setUp(self):
|
|
97
|
-
self.tmpdir = tempfile.mkdtemp()
|
|
98
|
-
run_es('init', '--dir', self.tmpdir)
|
|
99
|
-
# Add 3 evidence items from 2 sources
|
|
100
|
-
for src, quote in [
|
|
101
|
-
('src_aaa', 'First quote from source A.'),
|
|
102
|
-
('src_aaa', 'Second quote from source A.'),
|
|
103
|
-
('src_bbb', 'Quote from source B.'),
|
|
104
|
-
]:
|
|
105
|
-
run_es('add', '--json', json.dumps({
|
|
106
|
-
'source_id': src,
|
|
107
|
-
'quote': quote,
|
|
108
|
-
'evidence_type': 'direct_quote',
|
|
109
|
-
}), '--dir', self.tmpdir)
|
|
110
|
-
|
|
111
|
-
def tearDown(self):
|
|
112
|
-
shutil.rmtree(self.tmpdir, ignore_errors=True)
|
|
113
|
-
|
|
114
|
-
def test_list_all(self):
|
|
115
|
-
out = run_es('list', '--dir', self.tmpdir)
|
|
116
|
-
self.assertEqual(out['count'], 3)
|
|
117
|
-
|
|
118
|
-
def test_list_filtered(self):
|
|
119
|
-
out = run_es('list', '--dir', self.tmpdir, '--source-id', 'src_aaa')
|
|
120
|
-
self.assertEqual(out['count'], 2)
|
|
121
|
-
|
|
122
|
-
out = run_es('list', '--dir', self.tmpdir, '--source-id', 'src_bbb')
|
|
123
|
-
self.assertEqual(out['count'], 1)
|
|
124
|
-
|
|
125
|
-
def test_export(self):
|
|
126
|
-
out = run_es('export', '--dir', self.tmpdir)
|
|
127
|
-
self.assertIsInstance(out, list)
|
|
128
|
-
self.assertEqual(len(out), 3)
|
|
129
|
-
# Each has required fields
|
|
130
|
-
for row in out:
|
|
131
|
-
self.assertIn('evidence_id', row)
|
|
132
|
-
self.assertIn('source_id', row)
|
|
133
|
-
self.assertIn('quote', row)
|
|
134
|
-
self.assertIn('evidence_type', row)
|
|
135
|
-
self.assertIn('captured_at', row)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
class TestEvidenceID(unittest.TestCase):
|
|
139
|
-
"""Unit tests for compute_evidence_id."""
|
|
140
|
-
|
|
141
|
-
@classmethod
|
|
142
|
-
def setUpClass(cls):
|
|
143
|
-
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
|
|
144
|
-
from evidence_store import compute_evidence_id, normalize_quote
|
|
145
|
-
cls.compute_id = staticmethod(compute_evidence_id)
|
|
146
|
-
cls.normalize = staticmethod(normalize_quote)
|
|
147
|
-
|
|
148
|
-
def test_deterministic(self):
|
|
149
|
-
id1 = self.compute_id('src_a', 'test quote', 'page 1')
|
|
150
|
-
id2 = self.compute_id('src_a', 'test quote', 'page 1')
|
|
151
|
-
self.assertEqual(id1, id2)
|
|
152
|
-
|
|
153
|
-
def test_locator_matters(self):
|
|
154
|
-
id1 = self.compute_id('src_a', 'test quote', 'page 1')
|
|
155
|
-
id2 = self.compute_id('src_a', 'test quote', 'page 2')
|
|
156
|
-
self.assertNotEqual(id1, id2)
|
|
157
|
-
|
|
158
|
-
def test_normalize_whitespace(self):
|
|
159
|
-
self.assertEqual(
|
|
160
|
-
self.normalize(' hello world '),
|
|
161
|
-
'hello world',
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if __name__ == '__main__':
|
|
166
|
-
unittest.main()
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Tests for extract_claims.py CLI."""
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
|
-
import shutil
|
|
7
|
-
import subprocess
|
|
8
|
-
import sys
|
|
9
|
-
import tempfile
|
|
10
|
-
import unittest
|
|
11
|
-
|
|
12
|
-
SCRIPT = os.path.join(os.path.dirname(__file__), '..', 'scripts', 'extract_claims.py')
|
|
13
|
-
FIXTURES = os.path.join(os.path.dirname(__file__), 'fixtures')
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def run_ec(*args: str) -> dict | list:
|
|
17
|
-
"""Run extract_claims.py with args."""
|
|
18
|
-
result = subprocess.run(
|
|
19
|
-
[sys.executable, SCRIPT, *args],
|
|
20
|
-
capture_output=True, text=True,
|
|
21
|
-
)
|
|
22
|
-
if result.returncode != 0:
|
|
23
|
-
raise RuntimeError(f'Exit {result.returncode}: {result.stderr}')
|
|
24
|
-
return json.loads(result.stdout)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
SAMPLE_REPORT = """\
|
|
28
|
-
---
|
|
29
|
-
title: Test Research Report
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## Executive Summary
|
|
33
|
-
|
|
34
|
-
This report examines the impact of quantum computing on cryptography [1, 2]. The field has advanced significantly since 2020, with major breakthroughs in error correction.
|
|
35
|
-
|
|
36
|
-
## Introduction
|
|
37
|
-
|
|
38
|
-
Quantum computing represents a paradigm shift in computational capability. Researchers at Google demonstrated quantum supremacy in 2019 using a 53-qubit processor [3]. This milestone confirmed theoretical predictions made decades earlier.
|
|
39
|
-
|
|
40
|
-
## Finding 1
|
|
41
|
-
|
|
42
|
-
The Shor algorithm can factor large numbers exponentially faster than classical methods [4]. Current RSA-2048 encryption could be broken by a sufficiently large quantum computer. However, such machines are estimated to require millions of physical qubits [5, 6].
|
|
43
|
-
|
|
44
|
-
## Finding 2
|
|
45
|
-
|
|
46
|
-
Post-quantum cryptography standards should be adopted within the next 5 years. Organizations should consider hybrid classical-quantum approaches during the transition period. NIST has already standardized several lattice-based algorithms [7].
|
|
47
|
-
|
|
48
|
-
## Synthesis
|
|
49
|
-
|
|
50
|
-
Taken together, the evidence suggests that quantum computing poses a real but manageable threat to current cryptographic systems. The timeline for practical quantum attacks remains uncertain, but proactive migration reduces risk substantially.
|
|
51
|
-
|
|
52
|
-
## Recommendations
|
|
53
|
-
|
|
54
|
-
Organizations should begin evaluating post-quantum cryptography solutions immediately. Security teams should conduct a cryptographic inventory to identify vulnerable systems. Companies should consider implementing crypto-agility frameworks to enable rapid algorithm switching.
|
|
55
|
-
|
|
56
|
-
## Bibliography
|
|
57
|
-
|
|
58
|
-
[1] Smith et al. (2023). Quantum Computing Advances.
|
|
59
|
-
[2] Johnson (2024). Cryptographic Implications.
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class TestExtract(unittest.TestCase):
|
|
64
|
-
def setUp(self):
|
|
65
|
-
self.tmpdir = tempfile.mkdtemp()
|
|
66
|
-
# Create empty claims.jsonl
|
|
67
|
-
open(os.path.join(self.tmpdir, 'claims.jsonl'), 'w').close()
|
|
68
|
-
# Write sample report
|
|
69
|
-
self.report_path = os.path.join(self.tmpdir, 'report.md')
|
|
70
|
-
with open(self.report_path, 'w') as f:
|
|
71
|
-
f.write(SAMPLE_REPORT)
|
|
72
|
-
|
|
73
|
-
def tearDown(self):
|
|
74
|
-
shutil.rmtree(self.tmpdir, ignore_errors=True)
|
|
75
|
-
|
|
76
|
-
def test_extract_finds_claims(self):
|
|
77
|
-
out = run_ec('extract', '--report', self.report_path, '--dir', self.tmpdir)
|
|
78
|
-
self.assertEqual(out['status'], 'ok')
|
|
79
|
-
self.assertGreater(out['claims_added'], 5)
|
|
80
|
-
|
|
81
|
-
def test_extract_idempotent(self):
|
|
82
|
-
out1 = run_ec('extract', '--report', self.report_path, '--dir', self.tmpdir)
|
|
83
|
-
out2 = run_ec('extract', '--report', self.report_path, '--dir', self.tmpdir)
|
|
84
|
-
self.assertEqual(out2['claims_added'], 0)
|
|
85
|
-
self.assertEqual(out2['claims_skipped'], out1['claims_added'])
|
|
86
|
-
|
|
87
|
-
def test_claim_types_assigned(self):
|
|
88
|
-
run_ec('extract', '--report', self.report_path, '--dir', self.tmpdir)
|
|
89
|
-
out = run_ec('stats', '--dir', self.tmpdir)
|
|
90
|
-
# Should have at least factual and recommendation types
|
|
91
|
-
self.assertIn('factual', out['by_type'])
|
|
92
|
-
self.assertIn('recommendation', out['by_type'])
|
|
93
|
-
|
|
94
|
-
def test_sections_detected(self):
|
|
95
|
-
run_ec('extract', '--report', self.report_path, '--dir', self.tmpdir)
|
|
96
|
-
out = run_ec('stats', '--dir', self.tmpdir)
|
|
97
|
-
self.assertIn('finding_1', out['by_section'])
|
|
98
|
-
self.assertIn('finding_2', out['by_section'])
|
|
99
|
-
self.assertIn('recommendations', out['by_section'])
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class TestAdd(unittest.TestCase):
|
|
103
|
-
def setUp(self):
|
|
104
|
-
self.tmpdir = tempfile.mkdtemp()
|
|
105
|
-
open(os.path.join(self.tmpdir, 'claims.jsonl'), 'w').close()
|
|
106
|
-
|
|
107
|
-
def tearDown(self):
|
|
108
|
-
shutil.rmtree(self.tmpdir, ignore_errors=True)
|
|
109
|
-
|
|
110
|
-
def test_add_and_dedup(self):
|
|
111
|
-
claim = json.dumps({
|
|
112
|
-
'section_id': 'finding_1',
|
|
113
|
-
'text': 'Quantum computers can break RSA encryption.',
|
|
114
|
-
'claim_type': 'factual',
|
|
115
|
-
})
|
|
116
|
-
out1 = run_ec('add', '--json', claim, '--dir', self.tmpdir)
|
|
117
|
-
self.assertEqual(out1['status'], 'added')
|
|
118
|
-
self.assertEqual(len(out1['claim_id']), 16)
|
|
119
|
-
|
|
120
|
-
out2 = run_ec('add', '--json', claim, '--dir', self.tmpdir)
|
|
121
|
-
self.assertEqual(out2['status'], 'duplicate')
|
|
122
|
-
|
|
123
|
-
def test_add_with_sources(self):
|
|
124
|
-
claim = json.dumps({
|
|
125
|
-
'section_id': 'finding_1',
|
|
126
|
-
'text': 'NIST standardized CRYSTALS-Kyber in 2024.',
|
|
127
|
-
'claim_type': 'factual',
|
|
128
|
-
'cited_source_ids': ['abcdef0123456789'],
|
|
129
|
-
'evidence_ids': ['1234567890abcdef'],
|
|
130
|
-
})
|
|
131
|
-
out = run_ec('add', '--json', claim, '--dir', self.tmpdir)
|
|
132
|
-
self.assertEqual(out['status'], 'added')
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
class TestListAndStats(unittest.TestCase):
|
|
136
|
-
def setUp(self):
|
|
137
|
-
self.tmpdir = tempfile.mkdtemp()
|
|
138
|
-
open(os.path.join(self.tmpdir, 'claims.jsonl'), 'w').close()
|
|
139
|
-
# Add mixed claims
|
|
140
|
-
for sec, text, ctype in [
|
|
141
|
-
('finding_1', 'The sky appears blue due to Rayleigh scattering.', 'factual'),
|
|
142
|
-
('finding_1', 'Light wavelengths scatter differently in the atmosphere.', 'factual'),
|
|
143
|
-
('synthesis', 'Overall, atmospheric optics explains most visual phenomena.', 'synthesis'),
|
|
144
|
-
('recommendations', 'Researchers should investigate polarization effects further.', 'recommendation'),
|
|
145
|
-
]:
|
|
146
|
-
run_ec('add', '--json', json.dumps({
|
|
147
|
-
'section_id': sec, 'text': text, 'claim_type': ctype,
|
|
148
|
-
}), '--dir', self.tmpdir)
|
|
149
|
-
|
|
150
|
-
def tearDown(self):
|
|
151
|
-
shutil.rmtree(self.tmpdir, ignore_errors=True)
|
|
152
|
-
|
|
153
|
-
def test_list_all(self):
|
|
154
|
-
out = run_ec('list', '--dir', self.tmpdir)
|
|
155
|
-
self.assertEqual(out['count'], 4)
|
|
156
|
-
|
|
157
|
-
def test_list_by_section(self):
|
|
158
|
-
out = run_ec('list', '--dir', self.tmpdir, '--section', 'finding_1')
|
|
159
|
-
self.assertEqual(out['count'], 2)
|
|
160
|
-
|
|
161
|
-
def test_list_by_type(self):
|
|
162
|
-
out = run_ec('list', '--dir', self.tmpdir, '--type', 'recommendation')
|
|
163
|
-
self.assertEqual(out['count'], 1)
|
|
164
|
-
|
|
165
|
-
def test_stats(self):
|
|
166
|
-
out = run_ec('stats', '--dir', self.tmpdir)
|
|
167
|
-
self.assertEqual(out['total'], 4)
|
|
168
|
-
self.assertEqual(out['by_type']['factual'], 2)
|
|
169
|
-
self.assertEqual(out['by_type']['synthesis'], 1)
|
|
170
|
-
self.assertEqual(out['by_type']['recommendation'], 1)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
class TestClaimID(unittest.TestCase):
|
|
174
|
-
"""Unit tests for compute_claim_id."""
|
|
175
|
-
|
|
176
|
-
@classmethod
|
|
177
|
-
def setUpClass(cls):
|
|
178
|
-
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
|
|
179
|
-
from extract_claims import compute_claim_id, classify_claim
|
|
180
|
-
cls.compute_id = staticmethod(compute_claim_id)
|
|
181
|
-
cls.classify = staticmethod(classify_claim)
|
|
182
|
-
|
|
183
|
-
def test_deterministic(self):
|
|
184
|
-
id1 = self.compute_id('finding_1', 'Test claim.')
|
|
185
|
-
id2 = self.compute_id('finding_1', 'Test claim.')
|
|
186
|
-
self.assertEqual(id1, id2)
|
|
187
|
-
|
|
188
|
-
def test_section_matters(self):
|
|
189
|
-
id1 = self.compute_id('finding_1', 'Same text.')
|
|
190
|
-
id2 = self.compute_id('finding_2', 'Same text.')
|
|
191
|
-
self.assertNotEqual(id1, id2)
|
|
192
|
-
|
|
193
|
-
def test_classify_recommendation(self):
|
|
194
|
-
self.assertEqual(
|
|
195
|
-
self.classify('Organizations should adopt PQC immediately.', 'recommendations'),
|
|
196
|
-
'recommendation',
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
def test_classify_factual(self):
|
|
200
|
-
self.assertEqual(
|
|
201
|
-
self.classify('RSA-2048 uses 2048-bit keys.', 'finding_1'),
|
|
202
|
-
'factual',
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
def test_classify_synthesis(self):
|
|
206
|
-
self.assertEqual(
|
|
207
|
-
self.classify('Taken together, the results indicate a clear trend.', 'synthesis'),
|
|
208
|
-
'synthesis',
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if __name__ == '__main__':
|
|
213
|
-
unittest.main()
|