sneakoscope 2.0.11 → 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 (75) hide show
  1. package/README.md +9 -3
  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 +27 -8
  8. package/dist/core/agents/agent-orchestrator.js +279 -1
  9. package/dist/core/agents/agent-scheduler.js +12 -1
  10. package/dist/core/agents/agent-slot-pane-binding-proof.js +3 -3
  11. package/dist/core/agents/agent-work-queue.js +26 -2
  12. package/dist/core/agents/agent-worker-pipeline.js +2 -0
  13. package/dist/core/agents/native-cli-session-swarm.js +2 -2
  14. package/dist/core/codex-control/codex-sdk-adapter.js +10 -0
  15. package/dist/core/codex-control/codex-task-runner.js +4 -2
  16. package/dist/core/commands/naruto-command.js +104 -51
  17. package/dist/core/commands/research-command.js +43 -4
  18. package/dist/core/fsx.js +1 -1
  19. package/dist/core/git/git-worktree-merge-queue.js +34 -14
  20. package/dist/core/naruto/naruto-rebalance-policy.js +15 -3
  21. package/dist/core/naruto/naruto-work-graph.js +13 -0
  22. package/dist/core/research/claim-evidence-matrix.js +160 -0
  23. package/dist/core/research/experiment-plan.js +53 -0
  24. package/dist/core/research/falsification.js +18 -0
  25. package/dist/core/research/implementation-blueprint-markdown.js +31 -0
  26. package/dist/core/research/implementation-blueprint.js +66 -0
  27. package/dist/core/research/replication-pack.js +50 -0
  28. package/dist/core/research/research-cycle-runner.js +25 -0
  29. package/dist/core/research/research-final-reviewer.js +58 -0
  30. package/dist/core/research/research-handoff.js +51 -0
  31. package/dist/core/research/research-prompt-contract.js +24 -0
  32. package/dist/core/research/research-quality-contract.js +61 -0
  33. package/dist/core/research/research-report-quality.js +67 -0
  34. package/dist/core/research/research-stage-runner.js +16 -0
  35. package/dist/core/research/research-work-graph.js +75 -0
  36. package/dist/core/research/source-quality-report.js +94 -0
  37. package/dist/core/research.js +344 -44
  38. package/dist/core/version.js +1 -1
  39. package/dist/core/zellij/zellij-slot-column-anchor.js +165 -4
  40. package/dist/core/zellij/zellij-slot-pane-renderer.js +259 -16
  41. package/dist/core/zellij/zellij-worker-pane-manager.js +13 -7
  42. package/dist/scripts/agent-real-codex-in-zellij-worker-pane-check.js +8 -2
  43. package/dist/scripts/agent-slot-pane-binding-proof-check.js +4 -4
  44. package/dist/scripts/codex-sdk-release-review-pipeline-check.js +2 -1
  45. package/dist/scripts/codex-sdk-research-pipeline-check.js +7 -0
  46. package/dist/scripts/codex-sdk-zellij-pane-binding-check.js +2 -2
  47. package/dist/scripts/git-worktree-cross-rebase-check.js +13 -1
  48. package/dist/scripts/git-worktree-merge-queue-check.js +1 -0
  49. package/dist/scripts/local-collab-worktree-gpt-final-apply-policy-check.js +63 -0
  50. package/dist/scripts/naruto-actual-worker-control-plane-check.js +30 -3
  51. package/dist/scripts/naruto-allocation-runtime-wiring-check.js +92 -0
  52. package/dist/scripts/naruto-orchestrator-runtime-source-check.js +65 -6
  53. package/dist/scripts/naruto-rebalance-policy-check.js +15 -2
  54. package/dist/scripts/naruto-shadow-clone-swarm-check.js +1 -1
  55. package/dist/scripts/packlist-performance-check.js +1 -1
  56. package/dist/scripts/release-dag-full-coverage-check.js +4 -0
  57. package/dist/scripts/release-real-check.js +258 -77
  58. package/dist/scripts/research-quality-gate-check.js +86 -0
  59. package/dist/scripts/zellij-first-slot-down-stack-check.js +1 -1
  60. package/dist/scripts/zellij-first-slot-down-stack-real-check.js +344 -4
  61. package/dist/scripts/zellij-right-column-manager-check.js +1 -1
  62. package/dist/scripts/zellij-slot-column-anchor-check.js +45 -3
  63. package/dist/scripts/zellij-slot-only-ui-check.js +3 -1
  64. package/dist/scripts/zellij-slot-pane-renderer-check.js +73 -5
  65. package/dist/scripts/zellij-slot-renderer-proof-semantics-check.js +59 -0
  66. package/dist/scripts/zellij-worker-pane-manager-check.js +23 -1
  67. package/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +21 -4
  68. package/package.json +17 -2
  69. package/schemas/research/claim-evidence-matrix.schema.json +37 -0
  70. package/schemas/research/experiment-plan.schema.json +17 -0
  71. package/schemas/research/implementation-blueprint.schema.json +30 -0
  72. package/schemas/research/replication-pack.schema.json +17 -0
  73. package/schemas/research/research-final-review.schema.json +16 -0
  74. package/schemas/research/research-quality-contract.schema.json +37 -0
  75. package/schemas/research/source-quality-report.schema.json +18 -0
@@ -0,0 +1,160 @@
1
+ import path from 'node:path';
2
+ import { exists, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
3
+ export const CLAIM_EVIDENCE_MATRIX_ARTIFACT = 'claim-evidence-matrix.json';
4
+ export function defaultClaimEvidenceMatrix(missionId = '') {
5
+ return {
6
+ schema: 'sks.claim-evidence-matrix.v1',
7
+ mission_id: missionId,
8
+ claims: [],
9
+ key_claim_ids: [],
10
+ unsupported_claims: [],
11
+ triangulated_claim_count: 0,
12
+ blockers: []
13
+ };
14
+ }
15
+ export async function readClaimEvidenceMatrix(dir) {
16
+ const file = path.join(dir, CLAIM_EVIDENCE_MATRIX_ARTIFACT);
17
+ const present = await exists(file);
18
+ const matrix = normalizeClaimEvidenceMatrix(await readJson(file, null));
19
+ return {
20
+ present,
21
+ matrix,
22
+ key_claim_ids: matrix.key_claim_ids,
23
+ unsupported_claims: matrix.unsupported_claims,
24
+ triangulated_claim_count: matrix.triangulated_claim_count,
25
+ blockers: present ? matrix.blockers : ['claim_evidence_matrix_missing']
26
+ };
27
+ }
28
+ export async function writeClaimEvidenceMatrix(dir, matrix) {
29
+ const normalized = normalizeClaimEvidenceMatrix(matrix);
30
+ await writeJsonAtomic(path.join(dir, CLAIM_EVIDENCE_MATRIX_ARTIFACT), normalized);
31
+ return normalized;
32
+ }
33
+ export function validateClaimEvidenceMatrix(matrix, sourceLedger = null, falsificationLedger = null) {
34
+ const normalized = normalizeClaimEvidenceMatrix(matrix);
35
+ const claimIds = new Set(normalized.claims.map((claim) => claim.id));
36
+ const sourceIds = sourceIdSet(sourceLedger);
37
+ const counterIds = new Set([
38
+ ...Array.from(sourceIds).filter((id) => /counter/i.test(id)),
39
+ ...(Array.isArray(sourceLedger?.counterevidence_sources) ? sourceLedger.counterevidence_sources.map((row) => String(row?.id || '')).filter(Boolean) : []),
40
+ ...(Array.isArray(falsificationLedger?.cases) ? falsificationLedger.cases.flatMap((row) => [row?.id, ...(Array.isArray(row?.counterevidence_source_ids) ? row.counterevidence_source_ids : []), ...(Array.isArray(row?.source_ids) ? row.source_ids : [])]).map(String).filter(Boolean) : [])
41
+ ]);
42
+ const blockers = [];
43
+ for (const id of normalized.key_claim_ids)
44
+ if (!claimIds.has(id))
45
+ blockers.push(`key_claim_missing:${id}`);
46
+ for (const claim of normalized.claims) {
47
+ const important = claim.importance === 'high' || claim.importance === 'critical';
48
+ if (important && !claim.source_ids.length)
49
+ blockers.push(`claim_source_missing:${claim.id}`);
50
+ if (claim.importance === 'critical' && !claim.counterevidence_ids.length)
51
+ blockers.push(`critical_claim_counterevidence_missing:${claim.id}`);
52
+ for (const sourceId of claim.source_ids)
53
+ if (!sourceIds.has(sourceId))
54
+ blockers.push(`claim_source_unknown:${claim.id}:${sourceId}`);
55
+ for (const counterId of claim.counterevidence_ids)
56
+ if (!counterIds.has(counterId))
57
+ blockers.push(`claim_counterevidence_unknown:${claim.id}:${counterId}`);
58
+ if (claim.claim_type === 'hypothesis' && !claim.test_or_probe.trim())
59
+ blockers.push(`hypothesis_probe_missing:${claim.id}`);
60
+ }
61
+ for (const id of normalized.unsupported_claims) {
62
+ const claim = normalized.claims.find((row) => row.id === id);
63
+ if (claim?.importance === 'high' || claim?.importance === 'critical')
64
+ blockers.push(`unsupported_important_claim:${id}`);
65
+ }
66
+ return { ok: blockers.length === 0, blockers: [...new Set(blockers)] };
67
+ }
68
+ export function normalizeClaimEvidenceMatrix(value) {
69
+ const raw = value && typeof value === 'object' ? value : {};
70
+ const claims = (Array.isArray(raw.claims) ? raw.claims : []).map(normalizeClaim).filter((claim) => claim.id);
71
+ const keyClaimIds = normalizeStringList(raw.key_claim_ids).filter((id) => claims.some((claim) => claim.id === id));
72
+ const unsupported = normalizeStringList(raw.unsupported_claims);
73
+ return {
74
+ schema: 'sks.claim-evidence-matrix.v1',
75
+ mission_id: String(raw.mission_id || ''),
76
+ claims,
77
+ key_claim_ids: keyClaimIds,
78
+ unsupported_claims: unsupported,
79
+ triangulated_claim_count: Number.isFinite(Number(raw.triangulated_claim_count))
80
+ ? Math.max(0, Math.floor(Number(raw.triangulated_claim_count)))
81
+ : claims.filter((claim) => claim.triangulation.independent_confirmation_count >= 2 && claim.triangulation.source_layers.length >= 2).length,
82
+ blockers: normalizeStringList(raw.blockers)
83
+ };
84
+ }
85
+ export function buildClaimEvidenceMatrixFromLedgers(input = {}) {
86
+ const entries = Array.isArray(input.noveltyLedger?.entries) ? input.noveltyLedger.entries : [];
87
+ const sources = Array.isArray(input.sourceLedger?.sources) ? input.sourceLedger.sources : [];
88
+ const counterSources = Array.isArray(input.sourceLedger?.counterevidence_sources) ? input.sourceLedger.counterevidence_sources : [];
89
+ const fallbackSourceIds = sources.map((row) => String(row?.id || '')).filter(Boolean);
90
+ const fallbackCounterIds = counterSources.map((row) => String(row?.id || '')).filter(Boolean);
91
+ const claims = entries.map((entry, index) => {
92
+ const id = String(entry.id || `claim-${index + 1}`);
93
+ const sourceIds = normalizeStringList(entry.source_ids || entry.evidence).filter((sourceId) => fallbackSourceIds.includes(sourceId));
94
+ const counterIds = normalizeStringList(entry.counterevidence_ids || entry.falsifiers).filter((sourceId) => fallbackCounterIds.includes(sourceId));
95
+ return normalizeClaim({
96
+ id,
97
+ claim: entry.claim || entry.title || id,
98
+ claim_type: 'hypothesis',
99
+ importance: index < 2 ? 'critical' : 'high',
100
+ source_ids: sourceIds.length ? sourceIds : fallbackSourceIds.slice(0, 2),
101
+ counterevidence_ids: counterIds.length ? counterIds : fallbackCounterIds.slice(0, 1),
102
+ triangulation: {
103
+ source_layers: sourceLayersForSourceIds(input.sourceLedger, sourceIds.length ? sourceIds : fallbackSourceIds),
104
+ independent_confirmation_count: Math.max(1, sourceIds.length || fallbackSourceIds.length),
105
+ conflicts: []
106
+ },
107
+ confidence: entry.confidence >= 2 ? 'high' : 'medium',
108
+ falsifiable: true,
109
+ test_or_probe: entry.next_experiment || entry.test_or_probe || 'Run the proposed replication probe.'
110
+ });
111
+ });
112
+ return normalizeClaimEvidenceMatrix({
113
+ schema: 'sks.claim-evidence-matrix.v1',
114
+ mission_id: input.missionId || '',
115
+ claims,
116
+ key_claim_ids: claims.slice(0, 8).map((claim) => claim.id),
117
+ unsupported_claims: [],
118
+ triangulated_claim_count: claims.filter((claim) => claim.triangulation.source_layers.length >= 2).length,
119
+ blockers: []
120
+ });
121
+ }
122
+ function normalizeClaim(value) {
123
+ const importance = ['low', 'medium', 'high', 'critical'].includes(value?.importance) ? value.importance : 'medium';
124
+ const claimType = ['fact', 'inference', 'hypothesis', 'recommendation', 'implementation_guidance'].includes(value?.claim_type) ? value.claim_type : 'hypothesis';
125
+ const confidence = ['low', 'medium', 'high'].includes(value?.confidence) ? value.confidence : 'medium';
126
+ return {
127
+ id: String(value?.id || '').trim(),
128
+ claim: String(value?.claim || '').trim(),
129
+ claim_type: claimType,
130
+ importance,
131
+ source_ids: normalizeStringList(value?.source_ids),
132
+ local_evidence_ids: normalizeStringList(value?.local_evidence_ids),
133
+ counterevidence_ids: normalizeStringList(value?.counterevidence_ids),
134
+ triangulation: {
135
+ source_layers: normalizeStringList(value?.triangulation?.source_layers),
136
+ independent_confirmation_count: Math.max(0, Math.floor(Number(value?.triangulation?.independent_confirmation_count || 0))),
137
+ conflicts: normalizeStringList(value?.triangulation?.conflicts)
138
+ },
139
+ confidence,
140
+ falsifiable: value?.falsifiable !== false,
141
+ test_or_probe: String(value?.test_or_probe || '').trim()
142
+ };
143
+ }
144
+ function sourceIdSet(sourceLedger) {
145
+ return new Set([
146
+ ...(Array.isArray(sourceLedger?.sources) ? sourceLedger.sources : []),
147
+ ...(Array.isArray(sourceLedger?.counterevidence_sources) ? sourceLedger.counterevidence_sources : [])
148
+ ].map((row) => String(row?.id || '')).filter(Boolean));
149
+ }
150
+ function sourceLayersForSourceIds(sourceLedger, ids) {
151
+ const idSet = new Set(ids);
152
+ return [...new Set([
153
+ ...(Array.isArray(sourceLedger?.sources) ? sourceLedger.sources : []),
154
+ ...(Array.isArray(sourceLedger?.counterevidence_sources) ? sourceLedger.counterevidence_sources : [])
155
+ ].filter((row) => idSet.has(String(row?.id || ''))).map((row) => String(row?.layer || row?.source_layer || '')).filter(Boolean))];
156
+ }
157
+ function normalizeStringList(value) {
158
+ return [...new Set((Array.isArray(value) ? value : value == null ? [] : [value]).map((item) => String(item || '').trim()).filter(Boolean))];
159
+ }
160
+ //# sourceMappingURL=claim-evidence-matrix.js.map
@@ -0,0 +1,53 @@
1
+ import path from 'node:path';
2
+ import { nowIso, readJson, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
3
+ export const EXPERIMENT_PLAN_JSON_ARTIFACT = 'experiment-plan.json';
4
+ export const EXPERIMENT_PLAN_MARKDOWN_ARTIFACT = 'experiment-plan.md';
5
+ export function defaultExperimentPlan(plan = null) {
6
+ const prompt = String(plan?.prompt || 'research mission');
7
+ return {
8
+ schema: 'sks.research-experiment-plan.v1',
9
+ generated_at: nowIso(),
10
+ prompt,
11
+ hypothesis: 'The surviving research claim should produce a measurable improvement over a summary-only baseline.',
12
+ steps: [
13
+ { id: 'E1', action: 'Select one baseline output and one research-pipeline output for the same prompt.', expected_evidence: ['research-report.md'] },
14
+ { id: 'E2', action: 'Score cited key claims, triangulation, counterevidence, and unsupported claims.', expected_evidence: ['claim-evidence-matrix.json'] },
15
+ { id: 'E3', action: 'Run or design the smallest probe implied by the implementation blueprint.', expected_evidence: ['implementation-blueprint.json'] },
16
+ { id: 'E4', action: 'Compare failure cases and falsification outcomes.', expected_evidence: ['falsification-ledger.json'] },
17
+ { id: 'E5', action: 'Record replication commands, artifacts, and acceptance thresholds.', expected_evidence: ['replication-pack.json'] }
18
+ ],
19
+ metrics: ['key_claims_supported', 'triangulated_claims', 'counterevidence_sources', 'falsification_cases', 'experiment_steps'],
20
+ controls: ['summary_only_baseline', 'same_prompt_same_context'],
21
+ acceptance_threshold: 'All quality-contract thresholds are met and the final reviewer approves the run.'
22
+ };
23
+ }
24
+ export function validateExperimentPlan(experimentPlan = null, contract = null) {
25
+ const minSteps = Number(contract?.min_experiment_steps || 5);
26
+ const steps = Array.isArray(experimentPlan?.steps) ? experimentPlan.steps : [];
27
+ const completeSteps = steps.filter((step) => String(step?.id || '').trim() && String(step?.action || '').trim());
28
+ const blockers = [
29
+ ...(experimentPlan ? [] : ['experiment_plan_missing']),
30
+ ...(steps.length < minSteps ? ['experiment_plan_steps_below_contract'] : []),
31
+ ...(completeSteps.length < minSteps ? ['experiment_plan_too_thin'] : [])
32
+ ];
33
+ return { ok: blockers.length === 0, blockers, steps: steps.length, complete_steps: completeSteps.length, min_steps: minSteps };
34
+ }
35
+ export function renderExperimentPlanMarkdown(experimentPlan = null) {
36
+ const lines = ['# Research Experiment Plan', '', `Hypothesis: ${experimentPlan?.hypothesis || ''}`, '', '## Steps'];
37
+ for (const step of Array.isArray(experimentPlan?.steps) ? experimentPlan.steps : []) {
38
+ lines.push(`- ${step.id}: ${step.action}`);
39
+ }
40
+ lines.push('', '## Metrics');
41
+ for (const metric of Array.isArray(experimentPlan?.metrics) ? experimentPlan.metrics : [])
42
+ lines.push(`- ${metric}`);
43
+ return `${lines.join('\n')}\n`;
44
+ }
45
+ export async function readExperimentPlan(dir) {
46
+ return readJson(path.join(dir, EXPERIMENT_PLAN_JSON_ARTIFACT), null);
47
+ }
48
+ export async function writeExperimentPlan(dir, experimentPlan) {
49
+ await writeJsonAtomic(path.join(dir, EXPERIMENT_PLAN_JSON_ARTIFACT), experimentPlan);
50
+ await writeTextAtomic(path.join(dir, EXPERIMENT_PLAN_MARKDOWN_ARTIFACT), renderExperimentPlanMarkdown(experimentPlan));
51
+ return experimentPlan;
52
+ }
53
+ //# sourceMappingURL=experiment-plan.js.map
@@ -0,0 +1,18 @@
1
+ export function validateFalsificationCoverage(falsificationLedger = null, contract = null) {
2
+ const minCases = Number(contract?.min_falsification_cases || 4);
3
+ const cases = Array.isArray(falsificationLedger?.cases) ? falsificationLedger.cases : [];
4
+ const completeCases = cases.filter((entry) => {
5
+ return String(entry?.id || '').trim()
6
+ && String(entry?.target_claim || entry?.claim_id || '').trim()
7
+ && String(entry?.attack || entry?.counterexample || '').trim()
8
+ && Array.isArray(entry?.source_ids)
9
+ && entry.source_ids.length > 0
10
+ && String(entry?.next_decisive_test || '').trim();
11
+ });
12
+ const blockers = [
13
+ ...(cases.length < minCases ? ['falsification_cases_below_contract'] : []),
14
+ ...(completeCases.length < minCases ? ['falsification_cases_incomplete'] : [])
15
+ ];
16
+ return { ok: blockers.length === 0, blockers, cases: cases.length, complete_cases: completeCases.length, min_cases: minCases };
17
+ }
18
+ //# sourceMappingURL=falsification.js.map
@@ -0,0 +1,31 @@
1
+ export const IMPLEMENTATION_BLUEPRINT_MARKDOWN_ARTIFACT = 'implementation-blueprint.md';
2
+ export function renderImplementationBlueprintMarkdown(blueprint = null) {
3
+ const lines = [];
4
+ lines.push('# Research Implementation Blueprint');
5
+ lines.push('');
6
+ lines.push(`Prompt: ${blueprint?.prompt || ''}`);
7
+ lines.push(`Handoff route: ${blueprint?.handoff_route || '$Team'}`);
8
+ lines.push(`Implementation allowed in Research: ${blueprint?.implementation_allowed_in_research === true ? 'yes' : 'no'}`);
9
+ lines.push('');
10
+ lines.push('## Sections');
11
+ for (const section of Array.isArray(blueprint?.sections) ? blueprint.sections : []) {
12
+ lines.push(`### ${section.title || section.id}`);
13
+ lines.push('');
14
+ lines.push(String(section.detail || ''));
15
+ if (Array.isArray(section.acceptance_checks) && section.acceptance_checks.length) {
16
+ lines.push('');
17
+ lines.push('Acceptance checks:');
18
+ for (const check of section.acceptance_checks)
19
+ lines.push(`- ${check}`);
20
+ }
21
+ lines.push('');
22
+ }
23
+ if (Array.isArray(blueprint?.risks) && blueprint.risks.length) {
24
+ lines.push('## Risks');
25
+ for (const risk of blueprint.risks)
26
+ lines.push(`- ${risk}`);
27
+ lines.push('');
28
+ }
29
+ return `${lines.join('\n')}\n`;
30
+ }
31
+ //# sourceMappingURL=implementation-blueprint-markdown.js.map
@@ -0,0 +1,66 @@
1
+ import path from 'node:path';
2
+ import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
3
+ export const IMPLEMENTATION_BLUEPRINT_ARTIFACT = 'implementation-blueprint.json';
4
+ const DEFAULT_SECTION_IDS = Object.freeze([
5
+ 'problem',
6
+ 'decision',
7
+ 'architecture',
8
+ 'interfaces',
9
+ 'data_contracts',
10
+ 'execution_plan',
11
+ 'verification_plan',
12
+ 'risks_and_rollbacks'
13
+ ]);
14
+ export function defaultImplementationBlueprint(plan = null) {
15
+ const prompt = String(plan?.prompt || 'research mission');
16
+ return {
17
+ schema: 'sks.research-implementation-blueprint.v1',
18
+ generated_at: nowIso(),
19
+ prompt,
20
+ implementation_allowed_in_research: false,
21
+ handoff_route: '$Team',
22
+ sections: DEFAULT_SECTION_IDS.map((id, index) => ({
23
+ id,
24
+ title: id.split('_').map((part) => part[0]?.toUpperCase() + part.slice(1)).join(' '),
25
+ order: index + 1,
26
+ detail: `Research handoff detail for ${id} on: ${prompt}`,
27
+ evidence_claim_ids: [],
28
+ target_paths: [],
29
+ acceptance_checks: [`${id} is reviewed against cited research artifacts before implementation.`]
30
+ })),
31
+ dependencies: [],
32
+ out_of_scope: ['Repository source mutation during $Research runs.'],
33
+ open_questions: []
34
+ };
35
+ }
36
+ export function validateImplementationBlueprint(blueprint = null, contract = null) {
37
+ const minSections = Number(contract?.min_implementation_blueprint_sections || contract?.min_blueprint_sections || 8);
38
+ const sections = Array.isArray(blueprint?.sections) ? blueprint.sections : [];
39
+ const completeSections = sections.filter((section) => {
40
+ return String(section?.id || '').trim()
41
+ && String(section?.title || '').trim()
42
+ && String(section?.detail || '').trim()
43
+ && Array.isArray(section?.acceptance_checks)
44
+ && section.acceptance_checks.length > 0;
45
+ });
46
+ const blockers = [
47
+ ...(blueprint ? [] : ['implementation_blueprint_missing']),
48
+ ...(sections.length < minSections ? ['implementation_blueprint_sections_below_contract'] : []),
49
+ ...(completeSections.length < minSections ? ['implementation_blueprint_incomplete_sections'] : [])
50
+ ];
51
+ return {
52
+ ok: blockers.length === 0,
53
+ blockers,
54
+ sections: sections.length,
55
+ complete_sections: completeSections.length,
56
+ min_sections: minSections
57
+ };
58
+ }
59
+ export async function readImplementationBlueprint(dir) {
60
+ return readJson(path.join(dir, IMPLEMENTATION_BLUEPRINT_ARTIFACT), null);
61
+ }
62
+ export async function writeImplementationBlueprint(dir, blueprint) {
63
+ await writeJsonAtomic(path.join(dir, IMPLEMENTATION_BLUEPRINT_ARTIFACT), blueprint);
64
+ return blueprint;
65
+ }
66
+ //# sourceMappingURL=implementation-blueprint.js.map
@@ -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