sneakoscope 2.0.12 → 2.0.13

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 (43) hide show
  1. package/README.md +5 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/build-manifest.json +24 -8
  8. package/dist/core/codex-control/codex-sdk-adapter.js +10 -0
  9. package/dist/core/codex-control/codex-task-runner.js +4 -2
  10. package/dist/core/commands/research-command.js +43 -4
  11. package/dist/core/fsx.js +1 -1
  12. package/dist/core/research/claim-evidence-matrix.js +160 -0
  13. package/dist/core/research/experiment-plan.js +53 -0
  14. package/dist/core/research/falsification.js +18 -0
  15. package/dist/core/research/implementation-blueprint-markdown.js +31 -0
  16. package/dist/core/research/implementation-blueprint.js +66 -0
  17. package/dist/core/research/replication-pack.js +50 -0
  18. package/dist/core/research/research-cycle-runner.js +25 -0
  19. package/dist/core/research/research-final-reviewer.js +58 -0
  20. package/dist/core/research/research-handoff.js +51 -0
  21. package/dist/core/research/research-prompt-contract.js +24 -0
  22. package/dist/core/research/research-quality-contract.js +61 -0
  23. package/dist/core/research/research-report-quality.js +67 -0
  24. package/dist/core/research/research-stage-runner.js +16 -0
  25. package/dist/core/research/research-work-graph.js +75 -0
  26. package/dist/core/research/source-quality-report.js +94 -0
  27. package/dist/core/research.js +344 -44
  28. package/dist/core/version.js +1 -1
  29. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -3
  30. package/dist/core/zellij/zellij-slot-pane-renderer.js +259 -16
  31. package/dist/scripts/codex-sdk-research-pipeline-check.js +7 -0
  32. package/dist/scripts/packlist-performance-check.js +1 -1
  33. package/dist/scripts/research-quality-gate-check.js +86 -0
  34. package/dist/scripts/zellij-slot-column-anchor-check.js +26 -5
  35. package/dist/scripts/zellij-slot-pane-renderer-check.js +73 -5
  36. package/package.json +13 -1
  37. package/schemas/research/claim-evidence-matrix.schema.json +37 -0
  38. package/schemas/research/experiment-plan.schema.json +17 -0
  39. package/schemas/research/implementation-blueprint.schema.json +30 -0
  40. package/schemas/research/replication-pack.schema.json +17 -0
  41. package/schemas/research/research-final-review.schema.json +16 -0
  42. package/schemas/research/research-quality-contract.schema.json +37 -0
  43. package/schemas/research/source-quality-report.schema.json +18 -0
@@ -0,0 +1,50 @@
1
+ import path from 'node:path';
2
+ import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
3
+ export const REPLICATION_PACK_ARTIFACT = 'replication-pack.json';
4
+ export function defaultReplicationPack(plan = null) {
5
+ return {
6
+ schema: 'sks.research-replication-pack.v1',
7
+ generated_at: nowIso(),
8
+ mission_id: plan?.mission_id || null,
9
+ prompt: plan?.prompt || '',
10
+ inputs: ['research-plan.json', 'research-quality-contract.json', 'source-ledger.json', 'claim-evidence-matrix.json'],
11
+ commands: [
12
+ 'sks research status latest',
13
+ 'npm run research:quality-contract',
14
+ 'npm run research:claim-matrix',
15
+ 'npm run research:final-review'
16
+ ],
17
+ expected_artifacts: [
18
+ 'research-report.md',
19
+ 'claim-evidence-matrix.json',
20
+ 'source-quality-report.json',
21
+ 'implementation-blueprint.json',
22
+ 'experiment-plan.json',
23
+ 'replication-pack.json',
24
+ 'research-final-review.json',
25
+ 'research-gate.evaluated.json'
26
+ ],
27
+ assumptions: ['Live source retrieval must be recorded in source-ledger.json for real runs.'],
28
+ reproduction_notes: []
29
+ };
30
+ }
31
+ export function validateReplicationPack(replicationPack = null) {
32
+ const commands = Array.isArray(replicationPack?.commands) ? replicationPack.commands : [];
33
+ const artifacts = Array.isArray(replicationPack?.expected_artifacts) ? replicationPack.expected_artifacts : [];
34
+ const inputs = Array.isArray(replicationPack?.inputs) ? replicationPack.inputs : [];
35
+ const blockers = [
36
+ ...(replicationPack ? [] : ['replication_pack_missing']),
37
+ ...(commands.length < 3 ? ['replication_pack_commands_too_thin'] : []),
38
+ ...(artifacts.length < 6 ? ['replication_pack_artifacts_too_thin'] : []),
39
+ ...(inputs.length < 3 ? ['replication_pack_inputs_too_thin'] : [])
40
+ ];
41
+ return { ok: blockers.length === 0, blockers, commands: commands.length, artifacts: artifacts.length, inputs: inputs.length };
42
+ }
43
+ export async function readReplicationPack(dir) {
44
+ return readJson(path.join(dir, REPLICATION_PACK_ARTIFACT), null);
45
+ }
46
+ export async function writeReplicationPack(dir, replicationPack) {
47
+ await writeJsonAtomic(path.join(dir, REPLICATION_PACK_ARTIFACT), replicationPack);
48
+ return replicationPack;
49
+ }
50
+ //# sourceMappingURL=replication-pack.js.map
@@ -0,0 +1,25 @@
1
+ import path from 'node:path';
2
+ import { appendJsonlBounded, nowIso, writeJsonAtomic } from '../fsx.js';
3
+ import { runResearchStage } from './research-stage-runner.js';
4
+ export async function runResearchCycle(dir, graph = null, opts = {}) {
5
+ const startedAt = nowIso();
6
+ const stages = Array.isArray(graph?.work_items) ? graph.work_items : [];
7
+ const stageResults = [];
8
+ for (const stage of stages) {
9
+ stageResults.push(await runResearchStage(dir, stage, { status: opts.status || 'planned', startedAt }));
10
+ }
11
+ const record = {
12
+ schema: 'sks.research-cycle-runner.v1',
13
+ cycle: opts.cycle || 0,
14
+ readonly: true,
15
+ started_at: startedAt,
16
+ completed_at: nowIso(),
17
+ stage_count: stageResults.length,
18
+ status: opts.status || 'planned',
19
+ stages: stageResults.map((stage) => stage.stage_id)
20
+ };
21
+ await writeJsonAtomic(path.join(dir, 'research-cycle-runner.json'), record);
22
+ await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'research.cycle_runner.recorded', cycle: record.cycle, stage_count: record.stage_count });
23
+ return record;
24
+ }
25
+ //# sourceMappingURL=research-cycle-runner.js.map
@@ -0,0 +1,58 @@
1
+ import path from 'node:path';
2
+ import { nowIso, readJson, readText, writeJsonAtomic } from '../fsx.js';
3
+ import { analyzeResearchReportQuality } from './research-report-quality.js';
4
+ import { validateClaimEvidenceMatrix } from './claim-evidence-matrix.js';
5
+ import { validateImplementationBlueprint } from './implementation-blueprint.js';
6
+ import { validateExperimentPlan } from './experiment-plan.js';
7
+ import { validateReplicationPack } from './replication-pack.js';
8
+ import { validateFalsificationCoverage } from './falsification.js';
9
+ export const RESEARCH_FINAL_REVIEW_ARTIFACT = 'research-final-review.json';
10
+ export async function readResearchFinalReview(dir) {
11
+ return readJson(path.join(dir, RESEARCH_FINAL_REVIEW_ARTIFACT), null);
12
+ }
13
+ export async function runResearchFinalReviewer(dir, input = {}) {
14
+ const contract = input.contract || await readJson(path.join(dir, 'research-quality-contract.json'), null);
15
+ const sourceLedger = input.sourceLedger || await readJson(path.join(dir, 'source-ledger.json'), null);
16
+ const claimMatrix = input.claimMatrix || await readJson(path.join(dir, 'claim-evidence-matrix.json'), null);
17
+ const blueprint = input.blueprint || await readJson(path.join(dir, 'implementation-blueprint.json'), null);
18
+ const experimentPlan = input.experimentPlan || await readJson(path.join(dir, 'experiment-plan.json'), null);
19
+ const replicationPack = input.replicationPack || await readJson(path.join(dir, 'replication-pack.json'), null);
20
+ const falsificationLedger = input.falsificationLedger || await readJson(path.join(dir, 'falsification-ledger.json'), null);
21
+ const reportText = input.reportText || await readText(path.join(dir, 'research-report.md'), '');
22
+ const claimValidation = validateClaimEvidenceMatrix(claimMatrix, sourceLedger, falsificationLedger);
23
+ const blueprintValidation = validateImplementationBlueprint(blueprint, contract);
24
+ const experimentValidation = validateExperimentPlan(experimentPlan, contract);
25
+ const replicationValidation = validateReplicationPack(replicationPack);
26
+ const falsificationValidation = validateFalsificationCoverage(falsificationLedger, contract);
27
+ const reportQuality = analyzeResearchReportQuality(reportText);
28
+ const preliminaryReasons = Array.isArray(input.preliminaryReasons) ? input.preliminaryReasons : [];
29
+ const blockers = [
30
+ ...preliminaryReasons,
31
+ ...claimValidation.blockers,
32
+ ...blueprintValidation.blockers,
33
+ ...experimentValidation.blockers,
34
+ ...replicationValidation.blockers,
35
+ ...falsificationValidation.blockers,
36
+ ...reportQuality.blockers
37
+ ];
38
+ const uniqueBlockers = [...new Set(blockers)];
39
+ const review = {
40
+ schema: 'sks.research-final-reviewer.v1',
41
+ reviewed_at: nowIso(),
42
+ approved: uniqueBlockers.length === 0,
43
+ blockers: uniqueBlockers,
44
+ contract_summary: contract || null,
45
+ checks: {
46
+ claim_matrix: claimValidation,
47
+ implementation_blueprint: blueprintValidation,
48
+ experiment_plan: experimentValidation,
49
+ replication_pack: replicationValidation,
50
+ falsification: falsificationValidation,
51
+ report_quality: reportQuality
52
+ },
53
+ reviewer: 'research_final_reviewer_static_gate'
54
+ };
55
+ await writeJsonAtomic(path.join(dir, RESEARCH_FINAL_REVIEW_ARTIFACT), review);
56
+ return review;
57
+ }
58
+ //# sourceMappingURL=research-final-reviewer.js.map
@@ -0,0 +1,51 @@
1
+ import path from 'node:path';
2
+ import { nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
3
+ export const IMPLEMENTATION_HANDOFF_PATCH_PLAN_ARTIFACT = 'implementation-handoff.patch-plan.json';
4
+ export const TEAM_HANDOFF_GOAL_ARTIFACT = 'team-handoff-goal.md';
5
+ export const DECISION_LOG_ARTIFACT = 'decision-log.md';
6
+ export async function writeResearchHandoffArtifacts(dir, plan = null, blueprint = null) {
7
+ const patchPlan = {
8
+ schema: 'sks.research-implementation-handoff-patch-plan.v1',
9
+ generated_at: nowIso(),
10
+ mission_id: plan?.mission_id || null,
11
+ implementation_allowed_in_research: false,
12
+ intended_route: '$Team',
13
+ prompt: plan?.prompt || '',
14
+ source_artifacts: [
15
+ 'research-report.md',
16
+ 'claim-evidence-matrix.json',
17
+ 'implementation-blueprint.json',
18
+ 'experiment-plan.json',
19
+ 'replication-pack.json',
20
+ 'source-quality-report.json'
21
+ ],
22
+ proposed_changes: [],
23
+ notes: [
24
+ 'This is a handoff artifact. Research records implementation guidance but does not mutate repository source.'
25
+ ]
26
+ };
27
+ const goalLines = [
28
+ '# Research-To-Team Handoff Goal',
29
+ '',
30
+ `Mission: ${plan?.mission_id || 'unknown'}`,
31
+ `Prompt: ${plan?.prompt || ''}`,
32
+ '',
33
+ 'Use the implementation blueprint, claim-evidence matrix, source-quality report, experiment plan, replication pack, and final reviewer output before changing code.',
34
+ '',
35
+ 'Blueprint sections:',
36
+ ...(Array.isArray(blueprint?.sections) ? blueprint.sections.map((section) => `- ${section.id}: ${section.title}`) : [])
37
+ ];
38
+ const decisionLog = [
39
+ '# Research Decision Log',
40
+ '',
41
+ `Created: ${nowIso()}`,
42
+ '',
43
+ '- Research may write route-local artifacts only.',
44
+ '- Implementation decisions must be revalidated in the follow-up execution route.'
45
+ ];
46
+ await writeJsonAtomic(path.join(dir, IMPLEMENTATION_HANDOFF_PATCH_PLAN_ARTIFACT), patchPlan);
47
+ await writeTextAtomic(path.join(dir, TEAM_HANDOFF_GOAL_ARTIFACT), `${goalLines.join('\n')}\n`);
48
+ await writeTextAtomic(path.join(dir, DECISION_LOG_ARTIFACT), `${decisionLog.join('\n')}\n`);
49
+ return { patch_plan: patchPlan, goal_artifact: TEAM_HANDOFF_GOAL_ARTIFACT, decision_log: DECISION_LOG_ARTIFACT };
50
+ }
51
+ //# sourceMappingURL=research-handoff.js.map
@@ -0,0 +1,24 @@
1
+ export const RESEARCH_PROMPT_CONTRACT_LINES = Object.freeze([
2
+ 'QUALITY CONTRACT: satisfy research-quality-contract.json before setting research-gate.json passed=true.',
3
+ 'CLAIM MATRIX: create claim-evidence-matrix.json with key claims, source ids, counterevidence ids, triangulation, and hypothesis status.',
4
+ 'SOURCE QUALITY: create source-quality-report.json and record claim_ids on source-ledger sources.',
5
+ 'BLUEPRINT: create implementation-blueprint.json and implementation-blueprint.md with at least eight sections.',
6
+ 'EXPERIMENT: create experiment-plan.json/.md with at least five steps and a replication-pack.json.',
7
+ 'FINAL REVIEW: create research-final-review.json and keep the gate blocked unless approved=true.',
8
+ 'READ ONLY: do not mutate repository source during Research; write only route-local mission artifacts.'
9
+ ]);
10
+ export function researchPromptContractText() {
11
+ return RESEARCH_PROMPT_CONTRACT_LINES.join('\n');
12
+ }
13
+ export function validateResearchPromptContract(promptText = '') {
14
+ const text = String(promptText || '');
15
+ const requiredTokens = ['research-quality-contract.json', 'claim-evidence-matrix.json', 'source-quality-report.json', 'implementation-blueprint.json', 'experiment-plan.json', 'replication-pack.json', 'research-final-review.json'];
16
+ const missing = requiredTokens.filter((token) => !text.includes(token));
17
+ return {
18
+ ok: missing.length === 0,
19
+ blockers: missing.map((token) => `research_prompt_contract_missing:${token}`),
20
+ required_tokens: requiredTokens,
21
+ missing_tokens: missing
22
+ };
23
+ }
24
+ //# sourceMappingURL=research-prompt-contract.js.map
@@ -0,0 +1,61 @@
1
+ import path from 'node:path';
2
+ import { readJson, writeJsonAtomic } from '../fsx.js';
3
+ export const RESEARCH_QUALITY_CONTRACT_ARTIFACT = 'research-quality-contract.json';
4
+ export const DEFAULT_RESEARCH_QUALITY_CONTRACT = {
5
+ schema: 'sks.research-quality-contract.v1',
6
+ min_sources_total: 12,
7
+ min_source_layers_covered: 5,
8
+ min_counterevidence_sources: 2,
9
+ min_trianguled_claims: 6,
10
+ min_key_claims: 8,
11
+ min_implementation_blueprint_sections: 8,
12
+ min_falsification_cases: 4,
13
+ min_experiment_steps: 5,
14
+ min_report_words: 2200,
15
+ required_artifacts: [
16
+ 'research-report.md',
17
+ 'implementation-blueprint.md',
18
+ 'implementation-blueprint.json',
19
+ 'claim-evidence-matrix.json',
20
+ 'source-ledger.json',
21
+ 'source-quality-report.json',
22
+ 'falsification-ledger.json',
23
+ 'experiment-plan.md',
24
+ 'replication-pack.json',
25
+ 'research-gate.json'
26
+ ]
27
+ };
28
+ export function normalizeResearchQualityContract(input = {}) {
29
+ const source = input && typeof input === 'object' ? input : {};
30
+ return {
31
+ ...DEFAULT_RESEARCH_QUALITY_CONTRACT,
32
+ ...source,
33
+ schema: 'sks.research-quality-contract.v1',
34
+ min_sources_total: positiveInt(source.min_sources_total, DEFAULT_RESEARCH_QUALITY_CONTRACT.min_sources_total),
35
+ min_source_layers_covered: positiveInt(source.min_source_layers_covered, DEFAULT_RESEARCH_QUALITY_CONTRACT.min_source_layers_covered),
36
+ min_counterevidence_sources: positiveInt(source.min_counterevidence_sources, DEFAULT_RESEARCH_QUALITY_CONTRACT.min_counterevidence_sources),
37
+ min_trianguled_claims: positiveInt(source.min_trianguled_claims ?? source.min_triangulated_claims, DEFAULT_RESEARCH_QUALITY_CONTRACT.min_trianguled_claims),
38
+ min_key_claims: positiveInt(source.min_key_claims, DEFAULT_RESEARCH_QUALITY_CONTRACT.min_key_claims),
39
+ min_implementation_blueprint_sections: positiveInt(source.min_implementation_blueprint_sections, DEFAULT_RESEARCH_QUALITY_CONTRACT.min_implementation_blueprint_sections),
40
+ min_falsification_cases: positiveInt(source.min_falsification_cases, DEFAULT_RESEARCH_QUALITY_CONTRACT.min_falsification_cases),
41
+ min_experiment_steps: positiveInt(source.min_experiment_steps, DEFAULT_RESEARCH_QUALITY_CONTRACT.min_experiment_steps),
42
+ min_report_words: positiveInt(source.min_report_words, DEFAULT_RESEARCH_QUALITY_CONTRACT.min_report_words),
43
+ required_artifacts: Array.isArray(source.required_artifacts) && source.required_artifacts.length
44
+ ? source.required_artifacts.map(String)
45
+ : [...DEFAULT_RESEARCH_QUALITY_CONTRACT.required_artifacts]
46
+ };
47
+ }
48
+ export async function readResearchQualityContract(dir) {
49
+ const contract = await readJson(path.join(dir, RESEARCH_QUALITY_CONTRACT_ARTIFACT), null);
50
+ return normalizeResearchQualityContract(contract || {});
51
+ }
52
+ export async function writeResearchQualityContract(dir, contract = {}) {
53
+ const normalized = normalizeResearchQualityContract(contract);
54
+ await writeJsonAtomic(path.join(dir, RESEARCH_QUALITY_CONTRACT_ARTIFACT), normalized);
55
+ return normalized;
56
+ }
57
+ function positiveInt(value, fallback) {
58
+ const parsed = Number(value);
59
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
60
+ }
61
+ //# sourceMappingURL=research-quality-contract.js.map
@@ -0,0 +1,67 @@
1
+ export const REQUIRED_RESEARCH_REPORT_HEADINGS = [
2
+ 'Question',
3
+ 'Methodology',
4
+ 'Source Map',
5
+ 'Key Claims',
6
+ 'Evidence Matrix Summary',
7
+ 'Counterevidence',
8
+ 'Falsification',
9
+ 'Implementation Blueprint',
10
+ 'Experiment / Validation Plan',
11
+ 'Limitations',
12
+ 'References'
13
+ ];
14
+ export function analyzeResearchReportQuality(text) {
15
+ const body = String(text || '');
16
+ const headings = body.split(/\r?\n/)
17
+ .map((line) => line.match(/^#{1,6}\s+(.+?)\s*#*\s*$/)?.[1]?.trim())
18
+ .filter((value) => Boolean(value));
19
+ const lowerHeadings = headings.map((heading) => normalizeHeading(heading));
20
+ const headingsPresent = REQUIRED_RESEARCH_REPORT_HEADINGS.filter((heading) => lowerHeadings.some((value) => value.includes(normalizeHeading(heading))));
21
+ const missingHeadings = REQUIRED_RESEARCH_REPORT_HEADINGS.filter((heading) => !headingsPresent.includes(heading));
22
+ const implementationText = sectionText(body, 'Implementation Blueprint');
23
+ const referencesText = sectionText(body, 'References');
24
+ const referencesSourceIds = [...new Set([
25
+ ...body.matchAll(/\b(?:source|src|mock-source|counter|mock-counter)-[A-Za-z0-9_.:-]+\b/g)
26
+ ].map((match) => match[0]))];
27
+ const blockers = [
28
+ ...missingHeadings.map((heading) => `research_report_heading_missing:${normalizeHeading(heading).replace(/\s+/g, '_')}`),
29
+ ...(referencesSourceIds.length ? [] : ['research_report_references_missing_source_ids'])
30
+ ];
31
+ return {
32
+ schema: 'sks.research-report-quality.v1',
33
+ word_count: countWords(body),
34
+ headings_present: headingsPresent,
35
+ missing_headings: missingHeadings,
36
+ implementation_section_words: countWords(implementationText),
37
+ references_source_ids: referencesText ? referencesSourceIds : [],
38
+ blockers,
39
+ ok: blockers.length === 0
40
+ };
41
+ }
42
+ export function countWords(text) {
43
+ return String(text || '').trim().split(/\s+/).filter(Boolean).length;
44
+ }
45
+ function normalizeHeading(value) {
46
+ return String(value || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
47
+ }
48
+ function sectionText(text, heading) {
49
+ const lines = String(text || '').split(/\r?\n/);
50
+ const target = normalizeHeading(heading);
51
+ let capture = false;
52
+ const out = [];
53
+ for (const line of lines) {
54
+ const match = line.match(/^(#{1,6})\s+(.+?)\s*#*\s*$/);
55
+ if (match) {
56
+ const normalized = normalizeHeading(match[2] || '');
57
+ if (capture)
58
+ break;
59
+ capture = normalized.includes(target);
60
+ continue;
61
+ }
62
+ if (capture)
63
+ out.push(line);
64
+ }
65
+ return out.join('\n');
66
+ }
67
+ //# sourceMappingURL=research-report-quality.js.map
@@ -0,0 +1,16 @@
1
+ import path from 'node:path';
2
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
3
+ export async function runResearchStage(dir, stage, opts = {}) {
4
+ const record = {
5
+ schema: 'sks.research-stage-run.v1',
6
+ stage_id: stage?.id || opts.stageId || 'unknown',
7
+ status: opts.status || 'recorded',
8
+ readonly: true,
9
+ started_at: opts.startedAt || nowIso(),
10
+ completed_at: nowIso(),
11
+ notes: opts.notes || []
12
+ };
13
+ await writeJsonAtomic(path.join(dir, 'research', 'stages', `${record.stage_id}.json`), record);
14
+ return record;
15
+ }
16
+ //# sourceMappingURL=research-stage-runner.js.map
@@ -0,0 +1,75 @@
1
+ import path from 'node:path';
2
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
3
+ export const RESEARCH_WORK_GRAPH_ARTIFACT = 'research-work-graph.json';
4
+ const STAGES = [
5
+ { id: 'research_source_quality', title: 'Source quality and layered retrieval audit', kind: 'research', outputs: ['source-ledger.json', 'source-quality-report.json'] },
6
+ { id: 'research_claim_matrix', title: 'Claim-evidence matrix and citation coverage', kind: 'research', outputs: ['claim-evidence-matrix.json'] },
7
+ { id: 'research_falsification', title: 'Counterevidence and falsification strengthening', kind: 'research', outputs: ['falsification-ledger.json'] },
8
+ { id: 'research_synthesis_report', title: 'Research report and manuscript synthesis', kind: 'research', outputs: ['research-report.md'] },
9
+ { id: 'research_blueprint', title: 'Implementation blueprint and handoff', kind: 'documentation', outputs: ['implementation-blueprint.json', 'implementation-blueprint.md', 'team-handoff-goal.md'] },
10
+ { id: 'research_experiment', title: 'Experiment plan and replication pack', kind: 'verification', outputs: ['experiment-plan.json', 'replication-pack.json'] },
11
+ { id: 'research_final_review', title: 'Final reviewer quality audit', kind: 'verification', outputs: ['research-final-review.json'] },
12
+ { id: 'research_gate_close', title: 'Research gate evaluation and completion output', kind: 'final_review_input_pack', outputs: ['research-gate.evaluated.json', 'research-gate.json'] }
13
+ ];
14
+ function workItem(stage, index, plan = null) {
15
+ const missionPrefix = plan?.mission_id ? `.sneakoscope/missions/${plan.mission_id}/` : '';
16
+ return {
17
+ id: stage.id,
18
+ kind: stage.kind,
19
+ title: stage.title,
20
+ target_paths: stage.outputs.map((artifact) => `${missionPrefix}${artifact}`),
21
+ readonly_paths: [
22
+ `${missionPrefix}research-plan.json`,
23
+ `${missionPrefix}research-quality-contract.json`,
24
+ `${missionPrefix}source-ledger.json`,
25
+ `${missionPrefix}claim-evidence-matrix.json`
26
+ ],
27
+ write_paths: [],
28
+ required_role: index < 4 ? 'research' : 'verifier',
29
+ write_allowed: false,
30
+ verification_required: true,
31
+ dependencies: index === 0 ? [] : [STAGES[index - 1]?.id].filter(Boolean),
32
+ can_run_in_parallel_with: STAGES.filter((candidate) => candidate.id !== stage.id).map((candidate) => candidate.id),
33
+ conflicts_with: [],
34
+ estimated_cost: { tokens: 4000, latency_ms: 60000, cpu_weight: 1, memory_mb: 256, gpu_weight: 0 },
35
+ lease_requirements: stage.outputs.map((artifact) => ({ path: `${missionPrefix}${artifact}`, kind: 'read' })),
36
+ acceptance: { requires_patch_envelope: false, requires_verification: true, requires_gpt_final: false },
37
+ owner: null,
38
+ allocation_reason: 'Stage-aware read-only research pipeline work graph',
39
+ allocation_score: 1,
40
+ allocation_hints: { domains: [stage.kind], write_paths: [], read_only_paths: stage.outputs },
41
+ lane: null,
42
+ worktree: { mode: 'patch-envelope-only', required: false, allocation_required: false }
43
+ };
44
+ }
45
+ export function buildResearchWorkGraph(plan = null) {
46
+ const requestedClones = Math.max(8, Number(plan?.native_agent_plan?.session_count || 0));
47
+ const workItems = STAGES.map((stage, index) => workItem(stage, index, plan));
48
+ return {
49
+ schema: 'sks.naruto-work-graph.v1',
50
+ route: '$Naruto',
51
+ requested_clones: requestedClones,
52
+ total_work_items: workItems.length,
53
+ readonly: true,
54
+ write_capable: false,
55
+ work_items: workItems,
56
+ active_waves: [
57
+ { wave_id: 'research-quality-wave', work_item_ids: workItems.slice(0, 4).map((item) => item.id), write_paths: [], conflict_count: 0 },
58
+ { wave_id: 'research-closeout-wave', work_item_ids: workItems.slice(4).map((item) => item.id), write_paths: [], conflict_count: 0 }
59
+ ],
60
+ mixed_work_kinds: [...new Set(workItems.map((item) => item.kind))],
61
+ write_allowed_count: 0,
62
+ worktree_policy: { mode: 'patch-envelope-only', required: false, main_repo_root: null, worktree_root: null, fallback_reason: 'Research route is read-only.' },
63
+ blockers: [],
64
+ ok: true
65
+ };
66
+ }
67
+ export async function writeResearchWorkGraph(dir, plan = null) {
68
+ const graph = buildResearchWorkGraph(plan);
69
+ await writeJsonAtomic(path.join(dir, RESEARCH_WORK_GRAPH_ARTIFACT), {
70
+ ...graph,
71
+ generated_at: nowIso()
72
+ });
73
+ return graph;
74
+ }
75
+ //# sourceMappingURL=research-work-graph.js.map
@@ -0,0 +1,94 @@
1
+ import path from 'node:path';
2
+ import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
3
+ export const SOURCE_QUALITY_REPORT_ARTIFACT = 'source-quality-report.json';
4
+ const REQUIRED_SOURCE_FIELDS = Object.freeze([
5
+ 'id',
6
+ 'layer',
7
+ 'kind',
8
+ 'title',
9
+ 'locator',
10
+ 'publisher_or_author',
11
+ 'accessed_at',
12
+ 'reliability',
13
+ 'credibility',
14
+ 'stance',
15
+ 'claim_ids'
16
+ ]);
17
+ function asArray(value) {
18
+ return Array.isArray(value) ? value : [];
19
+ }
20
+ function sourceRows(sourceLedger = null) {
21
+ return [
22
+ ...asArray(sourceLedger?.sources),
23
+ ...asArray(sourceLedger?.counterevidence_sources)
24
+ ];
25
+ }
26
+ function missingFields(row) {
27
+ return REQUIRED_SOURCE_FIELDS.filter((field) => {
28
+ const value = row?.[field];
29
+ if (Array.isArray(value))
30
+ return value.length === 0;
31
+ return value === undefined || value === null || String(value).trim() === '';
32
+ });
33
+ }
34
+ export function buildSourceQualityReport(sourceLedger = null, claimMatrix = null) {
35
+ const rows = sourceRows(sourceLedger);
36
+ const rowReports = rows.map((row) => {
37
+ const missing = missingFields(row);
38
+ return {
39
+ id: String(row?.id || ''),
40
+ layer: row?.layer || row?.layer_id || row?.source_layer || null,
41
+ stance: row?.stance || null,
42
+ claim_ids: asArray(row?.claim_ids).map(String),
43
+ reliability: row?.reliability || null,
44
+ credibility: row?.credibility || null,
45
+ missing_fields: missing,
46
+ ok: missing.length === 0
47
+ };
48
+ });
49
+ const sourceLayerRows = asArray(sourceLedger?.source_layers);
50
+ const coveredLayerIds = sourceLayerRows
51
+ .filter((layer) => layer?.status === 'covered')
52
+ .map((layer) => String(layer.id || layer.layer || ''))
53
+ .filter(Boolean);
54
+ const keyClaimIds = asArray(claimMatrix?.key_claim_ids).map(String);
55
+ const citedClaimIds = new Set(rows.flatMap((row) => asArray(row?.claim_ids).map(String)));
56
+ const uncitedKeyClaimIds = keyClaimIds.filter((id) => !citedClaimIds.has(id));
57
+ const blockers = [
58
+ ...rowReports.flatMap((row) => row.ok ? [] : [`source_metadata_incomplete:${row.id || 'unknown'}`]),
59
+ ...(keyClaimIds.length && uncitedKeyClaimIds.length ? ['key_claim_citation_coverage_incomplete'] : []),
60
+ ...(sourceLedger?.citation_coverage?.all_key_claims_cited === false ? ['source_ledger_reports_uncited_key_claims'] : [])
61
+ ];
62
+ return {
63
+ schema: 'sks.research-source-quality-report.v1',
64
+ generated_at: nowIso(),
65
+ ok: blockers.length === 0,
66
+ blockers,
67
+ required_source_fields: [...REQUIRED_SOURCE_FIELDS],
68
+ summary: {
69
+ source_entries: asArray(sourceLedger?.sources).length,
70
+ counterevidence_entries: asArray(sourceLedger?.counterevidence_sources).length,
71
+ total_entries: rows.length,
72
+ rows_with_complete_metadata: rowReports.filter((row) => row.ok).length,
73
+ source_layers_covered: coveredLayerIds.length,
74
+ key_claims: keyClaimIds.length,
75
+ cited_key_claims: keyClaimIds.length - uncitedKeyClaimIds.length,
76
+ uncited_key_claims: uncitedKeyClaimIds.length
77
+ },
78
+ citation_coverage: {
79
+ all_key_claims_cited: uncitedKeyClaimIds.length === 0 && sourceLedger?.citation_coverage?.all_key_claims_cited === true,
80
+ cited_claim_ids: [...citedClaimIds].sort(),
81
+ uncited_key_claim_ids: uncitedKeyClaimIds
82
+ },
83
+ sources: rowReports
84
+ };
85
+ }
86
+ export async function readSourceQualityReport(dir) {
87
+ return readJson(path.join(dir, SOURCE_QUALITY_REPORT_ARTIFACT), null);
88
+ }
89
+ export async function writeSourceQualityReport(dir, sourceLedger = null, claimMatrix = null) {
90
+ const report = buildSourceQualityReport(sourceLedger, claimMatrix);
91
+ await writeJsonAtomic(path.join(dir, SOURCE_QUALITY_REPORT_ARTIFACT), report);
92
+ return report;
93
+ }
94
+ //# sourceMappingURL=source-quality-report.js.map