sneakoscope 2.0.13 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/core/agents/agent-orchestrator.js +11 -4
- package/dist/core/agents/agent-output-validator.js +1 -1
- package/dist/core/codex-control/codex-fake-sdk-adapter.js +3 -3
- package/dist/core/codex-control/gpt-final-review-schema.js +61 -14
- package/dist/core/commands/naruto-command.js +1 -0
- package/dist/core/commands/research-command.js +71 -17
- package/dist/core/fsx.js +1 -1
- package/dist/core/naruto/naruto-real-worker-child.js +11 -3
- package/dist/core/naruto/naruto-real-worker-runtime.js +4 -0
- package/dist/core/pipeline/final-gpt-patch-stage.js +20 -3
- package/dist/core/research/implementation-blueprint-densifier.js +124 -0
- package/dist/core/research/research-claim-builder.js +114 -0
- package/dist/core/research/research-cycle-runner.js +115 -11
- package/dist/core/research/research-final-reviewer.js +155 -1
- package/dist/core/research/research-source-ledger-merge.js +186 -0
- package/dist/core/research/research-source-shards.js +176 -0
- package/dist/core/research/research-stage-runner.js +510 -11
- package/dist/core/research/research-work-graph.js +114 -23
- package/dist/core/research.js +12 -0
- package/dist/core/version.js +1 -1
- package/dist/scripts/codex-sdk-research-pipeline-check.js +40 -8
- package/dist/scripts/release-dag-full-coverage-check.js +14 -1
- package/dist/scripts/release-parallel-speed-budget-check.js +7 -2
- package/dist/scripts/research-blueprint-densifier-check.js +21 -0
- package/dist/scripts/research-claim-builder-check.js +19 -0
- package/dist/scripts/research-complete-package-fixture-check.js +23 -0
- package/dist/scripts/research-final-reviewer-blackbox.js +22 -0
- package/dist/scripts/research-parallel-source-shards-check.js +22 -0
- package/dist/scripts/research-quality-gate-check.js +28 -3
- package/dist/scripts/research-real-cycle-no-legacy-final-md-check.js +14 -0
- package/dist/scripts/research-short-report-rejection-check.js +46 -0
- package/dist/scripts/research-source-ledger-merge-check.js +26 -0
- package/dist/scripts/research-stage-cycle-runtime-blackbox.js +24 -0
- package/package.json +16 -1
- package/schemas/codex/agent-result.schema.json +1 -1
- package/schemas/research/research-source-shard.schema.json +46 -0
- package/dist/build-manifest.json +0 -1184
- package/dist/scripts/release-readiness-report.js +0 -1146
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/permission-request.command.input.schema.json +0 -61
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/permission-request.command.output.schema.json +0 -103
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/post-compact.command.input.schema.json +0 -52
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/post-compact.command.output.schema.json +0 -24
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/post-tool-use.command.input.schema.json +0 -67
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/post-tool-use.command.output.schema.json +0 -84
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/pre-compact.command.input.schema.json +0 -52
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/pre-compact.command.output.schema.json +0 -24
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/pre-tool-use.command.input.schema.json +0 -65
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/pre-tool-use.command.output.schema.json +0 -105
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/session-start.command.input.schema.json +0 -59
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/session-start.command.output.schema.json +0 -63
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/snapshot-metadata.json +0 -31
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/stop.command.input.schema.json +0 -63
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/stop.command.output.schema.json +0 -45
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/user-prompt-submit.command.input.schema.json +0 -59
- package/dist/vendor/openai-codex/rust-v0.131.0/hooks/user-prompt-submit.command.output.schema.json +0 -81
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import fsp from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { writeSourceQualityReport } from './source-quality-report.js';
|
|
5
|
+
import { RESEARCH_SOURCE_LAYERS, validateResearchSourceShardOutput } from './research-source-shards.js';
|
|
6
|
+
export async function mergeResearchSourceShards(input) {
|
|
7
|
+
const shardDir = path.join(input.dir, 'research', `cycle-${input.cycle}`, 'source-shards');
|
|
8
|
+
const shardFiles = await listJsonFiles(shardDir);
|
|
9
|
+
const shardOutputs = [];
|
|
10
|
+
const blockers = [];
|
|
11
|
+
for (const file of shardFiles) {
|
|
12
|
+
const shard = await readJson(file, null);
|
|
13
|
+
const validation = validateResearchSourceShardOutput(shard);
|
|
14
|
+
if (!validation.ok)
|
|
15
|
+
blockers.push(...validation.blockers.map((blocker) => `${path.basename(file)}:${blocker}`));
|
|
16
|
+
shardOutputs.push(shard);
|
|
17
|
+
}
|
|
18
|
+
const requiredLayers = sourceLayersForPlan(input.plan);
|
|
19
|
+
const rows = dedupeSources(shardOutputs.flatMap((shard) => Array.isArray(shard?.sources) ? shard.sources : []));
|
|
20
|
+
const counterRows = rows.filter((row) => row.layer === 'counterevidence_factcheck' || row.stance === 'undermines');
|
|
21
|
+
const primaryRows = rows.filter((row) => !counterRows.some((counter) => counter.id === row.id));
|
|
22
|
+
const coveredLayers = [...new Set(rows.map((row) => String(row.layer || '')).filter(Boolean))];
|
|
23
|
+
const missingLayers = requiredLayers.map((layer) => layer.id).filter((id) => !coveredLayers.includes(id));
|
|
24
|
+
if (missingLayers.length)
|
|
25
|
+
blockers.push(...missingLayers.map((id) => `source_layer_missing:${id}`));
|
|
26
|
+
for (const shard of shardOutputs) {
|
|
27
|
+
if (Array.isArray(shard?.blockers))
|
|
28
|
+
blockers.push(...shard.blockers.map(String));
|
|
29
|
+
}
|
|
30
|
+
const sourceLedger = {
|
|
31
|
+
schema_version: 1,
|
|
32
|
+
policy: input.plan?.web_research_policy?.mode || 'layered_source_retrieval_and_triangulation',
|
|
33
|
+
created_at: nowIso(),
|
|
34
|
+
merged_at: nowIso(),
|
|
35
|
+
cycle: input.cycle,
|
|
36
|
+
source_layer_skill: {
|
|
37
|
+
artifact: 'research-source-skill.md',
|
|
38
|
+
status: 'created'
|
|
39
|
+
},
|
|
40
|
+
web_search_passes: shardOutputs.length ? 1 : 0,
|
|
41
|
+
source_layers: requiredLayers.map((layer) => {
|
|
42
|
+
const sourceIds = rows.filter((row) => row.layer === layer.id && row.stance !== 'undermines').map((row) => row.id);
|
|
43
|
+
const counterIds = rows.filter((row) => row.layer === layer.id && row.stance === 'undermines').map((row) => row.id);
|
|
44
|
+
return {
|
|
45
|
+
id: layer.id,
|
|
46
|
+
label: layer.label,
|
|
47
|
+
required: true,
|
|
48
|
+
status: sourceIds.length || counterIds.length ? 'covered' : 'missing',
|
|
49
|
+
evidence_role: layer.evidence_role,
|
|
50
|
+
query_templates: layer.query_templates || [],
|
|
51
|
+
source_ids: sourceIds,
|
|
52
|
+
counterevidence_ids: counterIds,
|
|
53
|
+
blocker: sourceIds.length || counterIds.length ? null : `source_layer_missing:${layer.id}`,
|
|
54
|
+
notes: sourceIds.length || counterIds.length ? 'Covered by source shard partials.' : 'No shard source rows were merged for this layer.'
|
|
55
|
+
};
|
|
56
|
+
}),
|
|
57
|
+
layer_coverage: {
|
|
58
|
+
required: requiredLayers.map((layer) => layer.id),
|
|
59
|
+
covered: coveredLayers,
|
|
60
|
+
missing: missingLayers,
|
|
61
|
+
notes: shardOutputs.map((shard) => `merged:${shard?.layer_id || 'unknown'}`)
|
|
62
|
+
},
|
|
63
|
+
queries: shardOutputs.flatMap((shard) => (Array.isArray(shard?.queries) ? shard.queries : []).map((query) => ({
|
|
64
|
+
layer: shard?.layer_id || null,
|
|
65
|
+
query: query?.query || '',
|
|
66
|
+
rationale: query?.rationale || '',
|
|
67
|
+
status: 'recorded'
|
|
68
|
+
}))),
|
|
69
|
+
sources: primaryRows,
|
|
70
|
+
counterevidence_sources: counterRows,
|
|
71
|
+
triangulation: {
|
|
72
|
+
cross_layer_checks: buildCrossLayerChecks(rows),
|
|
73
|
+
conflicts: counterRows.map((row) => ({ id: `conflict-${row.id}`, source_id: row.id, claim_ids: row.claim_ids || [], notes: row.notes || '' })),
|
|
74
|
+
synthesis_notes: ['Source shard partials merged before claim matrix build.']
|
|
75
|
+
},
|
|
76
|
+
quality_model: {
|
|
77
|
+
reporting_basis: 'Merged source shard rows preserve layer, kind, locator, publisher, access date, reliability, credibility, stance, and claim_ids.',
|
|
78
|
+
source_quality_fields: ['layer', 'kind', 'title', 'locator', 'publisher_or_author', 'published_at', 'accessed_at', 'reliability', 'credibility', 'stance', 'claim_ids']
|
|
79
|
+
},
|
|
80
|
+
citation_coverage: buildCitationCoverage(rows),
|
|
81
|
+
blockers: [...new Set(blockers)]
|
|
82
|
+
};
|
|
83
|
+
await writeJsonAtomic(path.join(input.dir, 'source-ledger.json'), sourceLedger);
|
|
84
|
+
const sourceQualityReportArtifact = 'source-quality-report.json';
|
|
85
|
+
await writeSourceQualityReport(input.dir, sourceLedger, await readJson(path.join(input.dir, 'claim-evidence-matrix.json'), null));
|
|
86
|
+
return {
|
|
87
|
+
ok: blockers.length === 0,
|
|
88
|
+
source_count: rows.length,
|
|
89
|
+
layer_count: coveredLayers.length,
|
|
90
|
+
blockers: [...new Set(blockers)]
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function listJsonFiles(dir) {
|
|
94
|
+
try {
|
|
95
|
+
const entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
96
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith('.json')).map((entry) => path.join(dir, entry.name)).sort();
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function sourceLayersForPlan(plan) {
|
|
103
|
+
const rows = Array.isArray(plan?.web_research_policy?.source_layers) ? plan.web_research_policy.source_layers : [];
|
|
104
|
+
const merged = [...rows, ...RESEARCH_SOURCE_LAYERS];
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
return merged.filter((layer) => {
|
|
107
|
+
const id = String(layer?.id || '');
|
|
108
|
+
if (!id || seen.has(id))
|
|
109
|
+
return false;
|
|
110
|
+
seen.add(id);
|
|
111
|
+
return true;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function dedupeSources(rows) {
|
|
115
|
+
const byKey = new Map();
|
|
116
|
+
for (const row of rows) {
|
|
117
|
+
const normalized = normalizeSourceRow(row);
|
|
118
|
+
if (!normalized.id)
|
|
119
|
+
continue;
|
|
120
|
+
const key = `${normalized.title}::${normalized.locator}`.toLowerCase();
|
|
121
|
+
const existing = byKey.get(key) || byKey.get(normalized.id);
|
|
122
|
+
if (existing) {
|
|
123
|
+
existing.claim_ids = [...new Set([...(existing.claim_ids || []), ...(normalized.claim_ids || [])])];
|
|
124
|
+
existing.notes = [existing.notes, normalized.notes].filter(Boolean).join('\n');
|
|
125
|
+
byKey.set(existing.id, existing);
|
|
126
|
+
byKey.set(key, existing);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
byKey.set(normalized.id, normalized);
|
|
130
|
+
byKey.set(key, normalized);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return [...new Map([...byKey.values()].map((row) => [row.id, row])).values()];
|
|
134
|
+
}
|
|
135
|
+
function normalizeSourceRow(row) {
|
|
136
|
+
return {
|
|
137
|
+
id: String(row?.id || '').trim(),
|
|
138
|
+
layer: String(row?.layer || row?.layer_id || '').trim(),
|
|
139
|
+
kind: String(row?.kind || 'source').trim(),
|
|
140
|
+
title: String(row?.title || row?.id || '').trim(),
|
|
141
|
+
locator: String(row?.locator || row?.url || '').trim(),
|
|
142
|
+
publisher_or_author: String(row?.publisher_or_author || row?.publisher || row?.author || '').trim(),
|
|
143
|
+
published_at: row?.published_at ? String(row.published_at) : undefined,
|
|
144
|
+
accessed_at: String(row?.accessed_at || nowIso()).trim(),
|
|
145
|
+
reliability: String(row?.reliability || 'unknown').trim(),
|
|
146
|
+
credibility: String(row?.credibility || 'unknown').trim(),
|
|
147
|
+
stance: ['supports', 'undermines', 'mixed', 'context'].includes(row?.stance) ? row.stance : 'context',
|
|
148
|
+
supports: normalizeStringList(row?.supports),
|
|
149
|
+
undermines: normalizeStringList(row?.undermines),
|
|
150
|
+
claim_ids: normalizeStringList(row?.claim_ids),
|
|
151
|
+
notes: String(row?.notes || '').trim()
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function buildCrossLayerChecks(rows) {
|
|
155
|
+
const byClaim = new Map();
|
|
156
|
+
for (const row of rows) {
|
|
157
|
+
for (const claimId of normalizeStringList(row.claim_ids)) {
|
|
158
|
+
const current = byClaim.get(claimId) || [];
|
|
159
|
+
current.push(row);
|
|
160
|
+
byClaim.set(claimId, current);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return [...byClaim.entries()].filter(([, claimRows]) => new Set(claimRows.map((row) => row.layer)).size >= 2).slice(0, 12).map(([claimId, claimRows], index) => ({
|
|
164
|
+
id: `source-shard-triangulation-${index + 1}`,
|
|
165
|
+
claim: claimId,
|
|
166
|
+
source_ids: claimRows.map((row) => row.id),
|
|
167
|
+
layers: [...new Set(claimRows.map((row) => row.layer))],
|
|
168
|
+
result: 'cross_layer_evidence_recorded'
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
function buildCitationCoverage(rows) {
|
|
172
|
+
const cited = [...new Set(rows.flatMap((row) => normalizeStringList(row.claim_ids)))];
|
|
173
|
+
const sourceClaimMap = Object.fromEntries(rows.map((row) => [row.id, normalizeStringList(row.claim_ids)]));
|
|
174
|
+
return {
|
|
175
|
+
all_key_claims_cited: cited.length >= 8,
|
|
176
|
+
key_claim_ids: cited.slice(0, 8),
|
|
177
|
+
cited_claim_ids: cited,
|
|
178
|
+
uncited_claim_ids: [],
|
|
179
|
+
source_claim_map: sourceClaimMap,
|
|
180
|
+
notes: ['Citation coverage was built from source shard claim_ids.']
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function normalizeStringList(value) {
|
|
184
|
+
return [...new Set((Array.isArray(value) ? value : value == null ? [] : [value]).map((item) => String(item || '').trim()).filter(Boolean))];
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=research-source-ledger-merge.js.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { nowIso } from '../fsx.js';
|
|
2
|
+
export const RESEARCH_SOURCE_LAYERS = Object.freeze([
|
|
3
|
+
{
|
|
4
|
+
id: 'academic_literature',
|
|
5
|
+
label: 'Academic literature',
|
|
6
|
+
purpose: 'Find papers, preprints, reviews, citations, and archival scholarly evidence before synthesis.',
|
|
7
|
+
evidence_role: 'formal_evidence',
|
|
8
|
+
examples: ['arXiv', 'Semantic Scholar', 'OpenAlex', 'Crossref', 'PubMed'],
|
|
9
|
+
query_templates: ['"<topic>" arxiv', '"<topic>" Semantic Scholar', '"<topic>" OpenAlex Crossref PubMed']
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: 'official_government_data',
|
|
13
|
+
label: 'Official government data',
|
|
14
|
+
purpose: 'Ground claims in public datasets, policy papers, national statistics, and leading-institution sources.',
|
|
15
|
+
evidence_role: 'authoritative_baseline',
|
|
16
|
+
examples: ['World Bank', 'OECD', 'Eurostat', 'data.gov', 'NIST'],
|
|
17
|
+
query_templates: ['"<topic>" site:worldbank.org OR site:oecd.org', '"<topic>" site:data.gov', '"<topic>" site:nist.gov']
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'standards_primary_docs',
|
|
21
|
+
label: 'Standards and primary documents',
|
|
22
|
+
purpose: 'Check specifications, standards, RFCs, policy originals, and official project documents before relying on summaries.',
|
|
23
|
+
evidence_role: 'primary_source',
|
|
24
|
+
examples: ['IETF RFCs', 'W3C', 'ISO abstracts', 'official standards bodies'],
|
|
25
|
+
query_templates: ['"<topic>" RFC standard specification', '"<topic>" W3C IETF NIST standard', '"<topic>" official specification']
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'news_current_events',
|
|
29
|
+
label: 'News and current events',
|
|
30
|
+
purpose: 'Capture recent events, public impact, and regional framing from reputable news and current-event indices.',
|
|
31
|
+
evidence_role: 'recency_signal',
|
|
32
|
+
examples: ['GDELT', 'BBC', 'Reuters', 'AP', 'regional reputable outlets'],
|
|
33
|
+
query_templates: ['"<topic>" latest Reuters AP', '"<topic>" GDELT news', '"<topic>" BBC analysis']
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'public_discourse',
|
|
37
|
+
label: 'Public discourse',
|
|
38
|
+
purpose: 'Sample public practitioner and community discourse without treating popularity as truth.',
|
|
39
|
+
evidence_role: 'sentiment_and_edge_cases',
|
|
40
|
+
examples: ['X/Twitter', 'Reddit', 'Hacker News', 'public forums'],
|
|
41
|
+
query_templates: ['"<topic>" site:x.com OR site:twitter.com', '"<topic>" site:reddit.com', '"<topic>" "Hacker News"']
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'developer_practitioner',
|
|
45
|
+
label: 'Developer and practitioner knowledge',
|
|
46
|
+
purpose: 'Find implementation pitfalls, developer questions, bug reports, and operational lessons.',
|
|
47
|
+
evidence_role: 'practice_feedback',
|
|
48
|
+
examples: ['Stack Overflow', 'Stack Exchange', 'GitHub issues', 'release notes', 'engineering blogs'],
|
|
49
|
+
query_templates: ['"<topic>" site:stackoverflow.com', '"<topic>" site:stackexchange.com', '"<topic>" site:github.com issues']
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'counterevidence_factcheck',
|
|
53
|
+
label: 'Counterevidence and fact checking',
|
|
54
|
+
purpose: 'Actively search for failures, critiques, null results, retractions, fact checks, and source conflicts.',
|
|
55
|
+
evidence_role: 'falsification',
|
|
56
|
+
examples: ['Fact checks', 'Retraction Watch', 'critical reviews', 'benchmark failures', 'negative results'],
|
|
57
|
+
query_templates: ['"<topic>" critique failure limitation', '"<topic>" fact check retraction', '"<topic>" counterevidence null result']
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'local_project_evidence',
|
|
61
|
+
label: 'Local project evidence',
|
|
62
|
+
purpose: 'Inspect repository-local files, scripts, docs, schemas, and tests as implementation evidence for handoff.',
|
|
63
|
+
evidence_role: 'local_evidence',
|
|
64
|
+
examples: ['git ls-files', 'package scripts', 'source modules', 'docs', 'schemas'],
|
|
65
|
+
query_templates: ['git ls-files', 'rg "<topic>" src docs schemas package.json']
|
|
66
|
+
}
|
|
67
|
+
]);
|
|
68
|
+
export function researchSourceLayerById(id) {
|
|
69
|
+
return RESEARCH_SOURCE_LAYERS.find((layer) => layer.id === id) || RESEARCH_SOURCE_LAYERS[0];
|
|
70
|
+
}
|
|
71
|
+
export function buildResearchSourceShardPrompt(plan, layer) {
|
|
72
|
+
return [
|
|
73
|
+
'You are executing one read-only SKS Research source shard.',
|
|
74
|
+
`Mission: ${plan?.mission_id || 'unknown'}`,
|
|
75
|
+
`Topic: ${plan?.prompt || ''}`,
|
|
76
|
+
`Source layer: ${layer.id} (${layer.label})`,
|
|
77
|
+
`Purpose: ${layer.purpose}`,
|
|
78
|
+
'',
|
|
79
|
+
'Return only JSON matching sks.research-source-shard-output.v1.',
|
|
80
|
+
'Do not modify source files. If live source access is unavailable, return blockers instead of inventing sources.',
|
|
81
|
+
'Every source row must include locator, publisher_or_author, accessed_at, reliability, credibility, stance, claim_ids, and notes.',
|
|
82
|
+
`Suggested query templates: ${layer.query_templates.join(' | ')}`
|
|
83
|
+
].join('\n');
|
|
84
|
+
}
|
|
85
|
+
export function defaultResearchSourceShardOutput(plan, layer, cycle = 1) {
|
|
86
|
+
const missionId = String(plan?.mission_id || '');
|
|
87
|
+
const topic = String(plan?.prompt || 'research mission');
|
|
88
|
+
const base = RESEARCH_SOURCE_LAYERS.findIndex((candidate) => candidate.id === layer.id);
|
|
89
|
+
const index = base >= 0 ? base : 0;
|
|
90
|
+
const firstClaim = layer.id === 'counterevidence_factcheck' ? 'stage-claim-1' : `stage-claim-${(index % 8) + 1}`;
|
|
91
|
+
const secondClaim = layer.id === 'counterevidence_factcheck' ? 'stage-claim-2' : `stage-claim-${((index + 1) % 8) + 1}`;
|
|
92
|
+
const primaryClaimIds = layer.id === 'counterevidence_factcheck' ? ['stage-claim-1', 'stage-claim-2', 'stage-claim-7'] : [firstClaim, secondClaim];
|
|
93
|
+
const secondaryClaimIds = layer.id === 'counterevidence_factcheck'
|
|
94
|
+
? ['stage-claim-1', 'stage-claim-2', 'stage-claim-8']
|
|
95
|
+
: layer.id === 'local_project_evidence' ? [secondClaim, 'stage-claim-7', 'stage-claim-8'] : [secondClaim];
|
|
96
|
+
const stance = layer.id === 'counterevidence_factcheck' ? 'undermines' : layer.id === 'local_project_evidence' ? 'context' : 'supports';
|
|
97
|
+
return {
|
|
98
|
+
schema: 'sks.research-source-shard-output.v1',
|
|
99
|
+
mission_id: missionId,
|
|
100
|
+
cycle,
|
|
101
|
+
layer_id: layer.id,
|
|
102
|
+
queries: layer.query_templates.slice(0, 3).map((query) => ({
|
|
103
|
+
query: query.replace(/<topic>/g, topic),
|
|
104
|
+
rationale: `Layer-specific query for ${layer.label}.`
|
|
105
|
+
})),
|
|
106
|
+
sources: [
|
|
107
|
+
{
|
|
108
|
+
id: `shard-${layer.id}-primary`,
|
|
109
|
+
layer: layer.id,
|
|
110
|
+
kind: layer.id === 'local_project_evidence' ? 'local_project' : 'deterministic_fixture',
|
|
111
|
+
title: `${layer.label} primary evidence for ${topic}`,
|
|
112
|
+
locator: layer.id === 'local_project_evidence' ? 'git ls-files' : `deterministic://${layer.id}/primary`,
|
|
113
|
+
publisher_or_author: layer.id === 'local_project_evidence' ? 'local repository' : 'SKS deterministic research shard',
|
|
114
|
+
published_at: nowIso().slice(0, 10),
|
|
115
|
+
accessed_at: nowIso(),
|
|
116
|
+
reliability: layer.id === 'public_discourse' ? 'medium' : 'high',
|
|
117
|
+
credibility: layer.id === 'public_discourse' ? 'contextual' : 'layer-appropriate',
|
|
118
|
+
stance,
|
|
119
|
+
claim_ids: primaryClaimIds,
|
|
120
|
+
notes: `${layer.label} shard records reproducible evidence metadata for ${topic}.`
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: `shard-${layer.id}-secondary`,
|
|
124
|
+
layer: layer.id,
|
|
125
|
+
kind: layer.id === 'local_project_evidence' ? 'local_project' : 'deterministic_fixture',
|
|
126
|
+
title: `${layer.label} secondary evidence for ${topic}`,
|
|
127
|
+
locator: layer.id === 'local_project_evidence' ? 'package.json docs src schemas' : `deterministic://${layer.id}/secondary`,
|
|
128
|
+
publisher_or_author: layer.id === 'local_project_evidence' ? 'local repository' : 'SKS deterministic research shard',
|
|
129
|
+
published_at: nowIso().slice(0, 10),
|
|
130
|
+
accessed_at: nowIso(),
|
|
131
|
+
reliability: 'medium',
|
|
132
|
+
credibility: 'corroborating',
|
|
133
|
+
stance: layer.id === 'counterevidence_factcheck' ? 'undermines' : 'mixed',
|
|
134
|
+
claim_ids: secondaryClaimIds,
|
|
135
|
+
notes: `${layer.label} shard adds a second row so merger and triangulation are observable.`
|
|
136
|
+
}
|
|
137
|
+
],
|
|
138
|
+
blockers: []
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function validateResearchSourceShardOutput(output) {
|
|
142
|
+
const blockers = [];
|
|
143
|
+
if (output?.schema !== 'sks.research-source-shard-output.v1')
|
|
144
|
+
blockers.push('source_shard_schema_invalid');
|
|
145
|
+
if (!String(output?.mission_id || '').trim())
|
|
146
|
+
blockers.push('source_shard_mission_missing');
|
|
147
|
+
if (!String(output?.layer_id || '').trim())
|
|
148
|
+
blockers.push('source_shard_layer_missing');
|
|
149
|
+
const sources = Array.isArray(output?.sources) ? output.sources : [];
|
|
150
|
+
const shardBlockers = Array.isArray(output?.blockers) ? output.blockers.filter(Boolean).map(String) : [];
|
|
151
|
+
if (!sources.length && !shardBlockers.length)
|
|
152
|
+
blockers.push('source_shard_empty_without_blocker');
|
|
153
|
+
for (const source of sources) {
|
|
154
|
+
for (const field of ['id', 'layer', 'kind', 'title', 'locator', 'publisher_or_author', 'accessed_at', 'reliability', 'credibility', 'stance', 'notes']) {
|
|
155
|
+
if (!String(source?.[field] || '').trim())
|
|
156
|
+
blockers.push(`source_shard_source_field_missing:${field}`);
|
|
157
|
+
}
|
|
158
|
+
if (!Array.isArray(source?.claim_ids) || source.claim_ids.length === 0)
|
|
159
|
+
blockers.push(`source_shard_claim_ids_missing:${source?.id || 'unknown'}`);
|
|
160
|
+
}
|
|
161
|
+
return { ok: blockers.length === 0, blockers: [...new Set(blockers)] };
|
|
162
|
+
}
|
|
163
|
+
export const researchSourceShardOutputSchema = {
|
|
164
|
+
type: 'object',
|
|
165
|
+
required: ['schema', 'mission_id', 'cycle', 'layer_id', 'queries', 'sources', 'blockers'],
|
|
166
|
+
properties: {
|
|
167
|
+
schema: { const: 'sks.research-source-shard-output.v1' },
|
|
168
|
+
mission_id: { type: 'string' },
|
|
169
|
+
cycle: { type: 'number' },
|
|
170
|
+
layer_id: { type: 'string' },
|
|
171
|
+
queries: { type: 'array' },
|
|
172
|
+
sources: { type: 'array' },
|
|
173
|
+
blockers: { type: 'array' }
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
//# sourceMappingURL=research-source-shards.js.map
|