sneakoscope 2.0.12 → 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 +5 -1
- 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/codex-sdk-adapter.js +10 -0
- package/dist/core/codex-control/codex-task-runner.js +4 -2
- 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 +112 -19
- 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/claim-evidence-matrix.js +160 -0
- package/dist/core/research/experiment-plan.js +53 -0
- package/dist/core/research/falsification.js +18 -0
- package/dist/core/research/implementation-blueprint-densifier.js +124 -0
- package/dist/core/research/implementation-blueprint-markdown.js +31 -0
- package/dist/core/research/implementation-blueprint.js +66 -0
- package/dist/core/research/replication-pack.js +50 -0
- package/dist/core/research/research-claim-builder.js +114 -0
- package/dist/core/research/research-cycle-runner.js +129 -0
- package/dist/core/research/research-final-reviewer.js +212 -0
- package/dist/core/research/research-handoff.js +51 -0
- package/dist/core/research/research-prompt-contract.js +24 -0
- package/dist/core/research/research-quality-contract.js +61 -0
- package/dist/core/research/research-report-quality.js +67 -0
- 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 +515 -0
- package/dist/core/research/research-work-graph.js +166 -0
- package/dist/core/research/source-quality-report.js +94 -0
- package/dist/core/research.js +356 -44
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-slot-column-anchor.js +5 -3
- package/dist/core/zellij/zellij-slot-pane-renderer.js +259 -16
- package/dist/scripts/codex-sdk-research-pipeline-check.js +44 -5
- package/dist/scripts/packlist-performance-check.js +1 -1
- 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 +111 -0
- 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/dist/scripts/zellij-slot-column-anchor-check.js +26 -5
- package/dist/scripts/zellij-slot-pane-renderer-check.js +73 -5
- package/package.json +28 -1
- package/schemas/codex/agent-result.schema.json +1 -1
- package/schemas/research/claim-evidence-matrix.schema.json +37 -0
- package/schemas/research/experiment-plan.schema.json +17 -0
- package/schemas/research/implementation-blueprint.schema.json +30 -0
- package/schemas/research/replication-pack.schema.json +17 -0
- package/schemas/research/research-final-review.schema.json +16 -0
- package/schemas/research/research-quality-contract.schema.json +37 -0
- package/schemas/research/research-source-shard.schema.json +46 -0
- package/schemas/research/source-quality-report.schema.json +18 -0
- package/dist/build-manifest.json +0 -1168
- 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
|
@@ -5,17 +5,18 @@ export function selectFinalGptPatchSource(gptFinal, localPatchEnvelopes = []) {
|
|
|
5
5
|
schema: 'sks.final-gpt-patch-stage.v1',
|
|
6
6
|
ok: true,
|
|
7
7
|
final_patch_source: 'gpt_final_arbiter',
|
|
8
|
-
patch_envelopes:
|
|
8
|
+
patch_envelopes: decodePatchDecisionItems(gptFinal?.result?.modified_patch_envelopes),
|
|
9
9
|
blockers: []
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
if (status === 'approved') {
|
|
13
|
+
const accepted = decodePatchDecisionItems(gptFinal?.result?.accepted_patch_envelopes);
|
|
13
14
|
return {
|
|
14
15
|
schema: 'sks.final-gpt-patch-stage.v1',
|
|
15
16
|
ok: true,
|
|
16
17
|
final_patch_source: 'gpt_final_arbiter',
|
|
17
|
-
patch_envelopes:
|
|
18
|
-
?
|
|
18
|
+
patch_envelopes: accepted.length
|
|
19
|
+
? accepted
|
|
19
20
|
: localPatchEnvelopes,
|
|
20
21
|
blockers: []
|
|
21
22
|
};
|
|
@@ -28,4 +29,20 @@ export function selectFinalGptPatchSource(gptFinal, localPatchEnvelopes = []) {
|
|
|
28
29
|
blockers: ['gpt_final_not_approved']
|
|
29
30
|
};
|
|
30
31
|
}
|
|
32
|
+
function decodePatchDecisionItems(value) {
|
|
33
|
+
if (!Array.isArray(value))
|
|
34
|
+
return [];
|
|
35
|
+
return value.flatMap((entry) => {
|
|
36
|
+
if (entry && typeof entry === 'object' && typeof entry.patch_envelope_json === 'string') {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(entry.patch_envelope_json);
|
|
39
|
+
return parsed && typeof parsed === 'object' && Object.keys(parsed).length ? [parsed] : [];
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return entry && typeof entry === 'object' ? [entry] : [];
|
|
46
|
+
});
|
|
47
|
+
}
|
|
31
48
|
//# sourceMappingURL=final-gpt-patch-stage.js.map
|
|
@@ -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,124 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { nowIso, runProcess } from '../fsx.js';
|
|
3
|
+
import {} from '../codex-control/codex-control-plane.js';
|
|
4
|
+
import { defaultImplementationBlueprint } from './implementation-blueprint.js';
|
|
5
|
+
export async function densifyImplementationBlueprint(input) {
|
|
6
|
+
const fileMap = await repositoryFileMap(input.root);
|
|
7
|
+
const likelyFiles = likelyTargetFiles(fileMap, input.plan, input.claimMatrix);
|
|
8
|
+
const possibleNewFiles = possibleNewResearchFiles(fileMap);
|
|
9
|
+
const base = input.existingBlueprint || defaultImplementationBlueprint(input.plan);
|
|
10
|
+
const claims = Array.isArray(input.claimMatrix?.claims) ? input.claimMatrix.claims : [];
|
|
11
|
+
const keyClaimIds = Array.isArray(input.claimMatrix?.key_claim_ids) ? input.claimMatrix.key_claim_ids : claims.slice(0, 8).map((claim) => claim.id);
|
|
12
|
+
const sections = [
|
|
13
|
+
section('problem', 'Problem', `Research currently must prove it executes stage-aware source shard runtime instead of relying on summary-style final.md output. The handoff should preserve ${keyClaimIds.length} key claim ids and all source-ledger evidence before code work begins.`, keyClaimIds, likelyFiles.slice(0, 8), ['Confirm the follow-up route reads claim-evidence-matrix.json and source-ledger.json before implementation.']),
|
|
14
|
+
section('decision', 'Decision', 'Use a dependency-aware research cycle with source_shard, source_merge, claim_matrix_build, falsification, implementation_blueprint, experiment_plan, synthesis, final_review, and verification stages. Keep Research read-only against repository source and write only mission artifacts.', keyClaimIds, likelyFiles.slice(0, 8), ['Default research run calls runResearchCycle; legacy final.md loop is opt-in only.']),
|
|
15
|
+
section('architecture', 'Architecture', 'The runtime is split into source shard generation, source-ledger merge, claim builder, blueprint densifier, final reviewer, blackbox scripts, and CLI status output. Each stage writes a ResearchStageResult under research/cycle-N/stages.', keyClaimIds, likelyFiles, ['Stage result artifacts list concrete output_artifacts for every passed stage.']),
|
|
16
|
+
section('interfaces', 'API And Schema Changes', `Existing files should expose typed contracts for shard outputs, stage results, merged source ledgers, Codex final review outputs, and concrete blueprint fields. Possible new files: ${possibleNewFiles.join(', ')}.`, keyClaimIds, likelyFiles, ['Schemas exist for research-source-shard and research-final-review.']),
|
|
17
|
+
section('data_contracts', 'Data Contracts', 'Source rows must preserve id, layer, kind, title, locator, publisher_or_author, accessed_at, reliability, credibility, stance, and claim_ids. Claim rows must preserve source_ids, counterevidence_ids, triangulation layers, confidence, and test_or_probe.', keyClaimIds, ['schemas/research/research-source-shard.schema.json', 'schemas/research/claim-evidence-matrix.schema.json'], ['Source quality report returns ok only when source metadata and citation coverage are complete.']),
|
|
18
|
+
section('execution_plan', 'Step By Step Implementation', implementationSteps(likelyFiles, possibleNewFiles).join('\n'), keyClaimIds, likelyFiles, ['Run research stage runtime blackbox, short-report rejection, complete-package fixture, and codex-sdk research pipeline gates.']),
|
|
19
|
+
section('verification_plan', 'Verification Plan', 'Run the release truth, research quality, source shard, source merge, claim builder, blueprint densifier, final reviewer, codex-sdk research pipeline, release DAG, and release check commands listed in the directive.', keyClaimIds, ['package.json', 'release-gates.v2.json', 'src/scripts/release-dag-full-coverage-check.ts'], ['All directive final checklist commands either pass or have a documented blocker.']),
|
|
20
|
+
section('risks_and_rollbacks', 'Risks And Rollbacks', 'The main risk is accepting deterministic fixture text as public-ready proof. Roll back by disabling new release gates only if the gate itself is wrong, not if implementation is incomplete. Research must block when live Codex/GPT final review is unavailable outside mock fixtures.', keyClaimIds, likelyFiles, ['A rollback keeps source mutation outside Research and restores package version metadata consistently.'])
|
|
21
|
+
];
|
|
22
|
+
return {
|
|
23
|
+
...base,
|
|
24
|
+
schema: 'sks.research-implementation-blueprint.v1',
|
|
25
|
+
generated_at: nowIso(),
|
|
26
|
+
prompt: input.plan?.prompt || base.prompt || '',
|
|
27
|
+
implementation_allowed_in_research: false,
|
|
28
|
+
handoff_route: '$Team',
|
|
29
|
+
repository_aware: true,
|
|
30
|
+
existing_files: likelyFiles,
|
|
31
|
+
possible_new_files: possibleNewFiles,
|
|
32
|
+
api_schema_changes: [
|
|
33
|
+
'ResearchStageResult contract for every executed stage.',
|
|
34
|
+
'ResearchSourceShardOutput contract for source layer partials.',
|
|
35
|
+
'Codex/GPT final reviewer merged with static review.'
|
|
36
|
+
],
|
|
37
|
+
test_commands: [
|
|
38
|
+
'npm run research:stage-cycle-runtime-blackbox',
|
|
39
|
+
'npm run research:short-report-rejection',
|
|
40
|
+
'npm run research:complete-package-fixture',
|
|
41
|
+
'npm run codex-sdk:research-pipeline',
|
|
42
|
+
'npm run release:check'
|
|
43
|
+
],
|
|
44
|
+
rollback_steps: [
|
|
45
|
+
'Revert only the files listed in the follow-up patch plan.',
|
|
46
|
+
'Restore package version metadata with npm install --package-lock-only if package-lock drift occurs.',
|
|
47
|
+
'Run npm run release:version-truth and the research blackbox gates after rollback.'
|
|
48
|
+
],
|
|
49
|
+
parallel_work_decomposition: [
|
|
50
|
+
'WS-A stage runtime and research run integration.',
|
|
51
|
+
'WS-B source shards and ledger merge.',
|
|
52
|
+
'WS-C claim matrix builder.',
|
|
53
|
+
'WS-D blueprint and handoff densifier.',
|
|
54
|
+
'WS-E final reviewer.',
|
|
55
|
+
'WS-F blackbox gates and release DAG.',
|
|
56
|
+
'WS-G CLI/docs.',
|
|
57
|
+
'WS-H integration and verification.'
|
|
58
|
+
],
|
|
59
|
+
sections,
|
|
60
|
+
dependencies: ['claim-evidence-matrix.json', 'source-ledger.json', 'falsification-ledger.json'],
|
|
61
|
+
out_of_scope: ['Repository source mutation during $Research runs.'],
|
|
62
|
+
open_questions: []
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function repositoryFileMap(root) {
|
|
66
|
+
const result = await runProcess('git', ['ls-files'], { cwd: root, timeoutMs: 15000, maxOutputBytes: 2 * 1024 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
|
|
67
|
+
return String(result.stdout || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
68
|
+
}
|
|
69
|
+
function likelyTargetFiles(files, plan, claimMatrix) {
|
|
70
|
+
const promptTerms = new Set(String(plan?.prompt || '').toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 3));
|
|
71
|
+
const guidanceTerms = new Set((Array.isArray(claimMatrix?.claims) ? claimMatrix.claims : []).flatMap((claim) => String(claim?.claim || '').toLowerCase().split(/[^a-z0-9]+/)).filter((term) => term.length > 5));
|
|
72
|
+
const preferred = [
|
|
73
|
+
'src/core/research/research-cycle-runner.ts',
|
|
74
|
+
'src/core/research/research-stage-runner.ts',
|
|
75
|
+
'src/core/research/research-work-graph.ts',
|
|
76
|
+
'src/core/commands/research-command.ts',
|
|
77
|
+
'src/core/research/claim-evidence-matrix.ts',
|
|
78
|
+
'src/core/research/implementation-blueprint.ts',
|
|
79
|
+
'src/core/research/research-final-reviewer.ts',
|
|
80
|
+
'package.json',
|
|
81
|
+
'release-gates.v2.json',
|
|
82
|
+
'docs/research-pipeline.md',
|
|
83
|
+
'docs/research-artifacts.md',
|
|
84
|
+
'docs/research-implementation-handoff.md'
|
|
85
|
+
].filter((file) => files.includes(file));
|
|
86
|
+
const matched = files.filter((file) => {
|
|
87
|
+
const lower = file.toLowerCase();
|
|
88
|
+
return lower.includes('research') || [...promptTerms, ...guidanceTerms].some((term) => lower.includes(term));
|
|
89
|
+
}).slice(0, 30);
|
|
90
|
+
return [...new Set([...preferred, ...matched])].slice(0, 40);
|
|
91
|
+
}
|
|
92
|
+
function possibleNewResearchFiles(files) {
|
|
93
|
+
return [
|
|
94
|
+
'src/core/research/research-source-shards.ts',
|
|
95
|
+
'src/core/research/research-source-ledger-merge.ts',
|
|
96
|
+
'src/core/research/research-claim-builder.ts',
|
|
97
|
+
'src/core/research/implementation-blueprint-densifier.ts',
|
|
98
|
+
'src/scripts/research-stage-cycle-runtime-blackbox.ts',
|
|
99
|
+
'src/scripts/research-short-report-rejection-check.ts',
|
|
100
|
+
'schemas/research/research-source-shard.schema.json'
|
|
101
|
+
].filter((file) => !files.includes(file));
|
|
102
|
+
}
|
|
103
|
+
function section(id, title, detail, claimIds, targetPaths, acceptanceChecks) {
|
|
104
|
+
return {
|
|
105
|
+
id,
|
|
106
|
+
title,
|
|
107
|
+
order: ['problem', 'decision', 'architecture', 'interfaces', 'data_contracts', 'execution_plan', 'verification_plan', 'risks_and_rollbacks'].indexOf(id) + 1,
|
|
108
|
+
detail,
|
|
109
|
+
evidence_claim_ids: claimIds.slice(0, 8),
|
|
110
|
+
target_paths: targetPaths,
|
|
111
|
+
acceptance_checks: acceptanceChecks
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function implementationSteps(existingFiles, newFiles) {
|
|
115
|
+
return [
|
|
116
|
+
`1. Update runtime files: ${existingFiles.filter((file) => file.includes('research-cycle-runner') || file.includes('research-stage-runner') || file.includes('research-work-graph')).join(', ')}.`,
|
|
117
|
+
`2. Add or verify source/claim/blueprint helper files: ${newFiles.filter((file) => file.includes('research')).join(', ')}.`,
|
|
118
|
+
'3. Wire sks research run so default execution uses runResearchCycle and the final.md Codex exec loop is legacy-only.',
|
|
119
|
+
'4. Add blackbox scripts that create temporary missions and verify rejection/pass/runtime behavior.',
|
|
120
|
+
'5. Update package scripts, release-gates.v2.json, docs, changelog, and version metadata.',
|
|
121
|
+
'6. Run the directive final checklist and record any hard blocker instead of claiming completion.'
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=implementation-blueprint-densifier.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
|