diff --git a/causal-claim-validity-guard/README.md b/causal-claim-validity-guard/README.md
new file mode 100644
index 00000000..fbe4195c
--- /dev/null
+++ b/causal-claim-validity-guard/README.md
@@ -0,0 +1,48 @@
+# Causal Claim Validity Guard
+
+This module adds a focused causal-claim review gate for the AI-Powered Research Assistant Suite in issue `#16`.
+
+Scientific manuscripts often drift from careful associational language into causal conclusions. The guard inspects synthetic review packets and determines whether AI-generated peer-review output should be released, revised, or held until the causal claim is supported.
+
+## Scope
+
+- Detects causal-language overreach in manuscript claims and conclusions.
+- Checks whether the study design can support causal language.
+- Flags missing comparator/control support, temporal-order gaps, confounder handling gaps, and effect-size uncertainty.
+- Produces evidence-linked remediation items suitable for authors, internal reviewers, or automated pre-submission checks.
+- Uses synthetic data only.
+- Uses no external AI APIs, credentials, private manuscripts, payment systems, or network calls.
+
+## Requirement Map
+
+| Issue #16 capability | Implementation |
+| --- | --- |
+| Auto peer review reports | Emits structured reviewer findings, severity, decision, and remediation. |
+| Methodological red flags | Detects design/evidence mismatch, missing controls, confounder gaps, and uncertainty gaps. |
+| Claims vs evidence alignment | Compares claim intent, causal wording, study design, temporal evidence, and comparator evidence. |
+| Adaptive templates per domain | Supports packet-level `domain` and domain-specific reviewer note generation. |
+| Reproducible deterministic output | Demo and tests use fixed synthetic packets and deterministic scoring. |
+
+## Run
+
+```bash
+npm test
+npm run demo
+npm run check
+```
+
+Demo artifacts are written to `artifacts/`:
+
+- `causal-claim-review.json`
+- `causal-claim-review.md`
+- `causal-claim-summary.svg`
+
+## Decisions
+
+- `RELEASE`: evidence supports the causal wording.
+- `REVISE`: the manuscript can likely be repaired with clearer language or additional evidence.
+- `HOLD`: the causal claim is not safe to release without material methodological support.
+
+## AI Assistance Disclosure
+
+Implemented with Codex assistance and locally verified with deterministic tests and generated artifacts.
diff --git a/causal-claim-validity-guard/artifacts/causal-claim-review.json b/causal-claim-validity-guard/artifacts/causal-claim-review.json
new file mode 100644
index 00000000..6d2a7c8a
--- /dev/null
+++ b/causal-claim-validity-guard/artifacts/causal-claim-review.json
@@ -0,0 +1,129 @@
+{
+ "generatedAt": "2026-08-15T20:24:14.343Z",
+ "packetCount": 4,
+ "decisionCounts": {
+ "RELEASE": 2,
+ "REVISE": 1,
+ "HOLD": 1
+ },
+ "reviews": [
+ {
+ "id": "cardio-rct-001",
+ "title": "Remote coaching reduces systolic blood pressure in high-risk adults",
+ "domain": "clinical trials",
+ "decision": "RELEASE",
+ "confidence": 0.95,
+ "causalLanguageDetected": true,
+ "findingCount": 0,
+ "findings": [],
+ "reviewerNote": "clinical trials: causal wording is aligned with the study design and evidence packet."
+ },
+ {
+ "id": "education-cross-sectional-002",
+ "title": "Daily tutoring app usage improves mathematics scores",
+ "domain": "education",
+ "decision": "HOLD",
+ "confidence": 0.15,
+ "causalLanguageDetected": true,
+ "findingCount": 5,
+ "findings": [
+ {
+ "code": "DESIGN_CANNOT_SUPPORT_CAUSAL_LANGUAGE",
+ "severity": "critical",
+ "message": "The manuscript uses causal language, but the stated design does not independently support causal inference.",
+ "evidence": {
+ "design": "cross_sectional",
+ "claim": "Daily tutoring app usage improves mathematics scores."
+ },
+ "remediation": "Replace causal verbs with associational language or add a causal identification strategy with assumptions and diagnostics."
+ },
+ {
+ "code": "MISSING_COMPARATOR",
+ "severity": "major",
+ "message": "Causal review requires a comparator, control, counterfactual, or explicit quasi-experimental contrast.",
+ "evidence": {
+ "hasComparator": false
+ },
+ "remediation": "Add a comparator/control description and explain why it approximates the counterfactual."
+ },
+ {
+ "code": "TEMPORAL_ORDER_GAP",
+ "severity": "major",
+ "message": "The evidence packet does not show that exposure or intervention preceded the outcome.",
+ "evidence": {
+ "temporalOrder": false
+ },
+ "remediation": "Document timing of exposure, intervention, measurement, and outcome windows."
+ },
+ {
+ "code": "CONFOUNDER_STRATEGY_MISSING",
+ "severity": "major",
+ "message": "No confounder strategy is described for a causal claim.",
+ "evidence": {
+ "confounderStrategy": null
+ },
+ "remediation": "Describe randomization, matching, adjustment, stratification, sensitivity analysis, or a defensible natural experiment."
+ },
+ {
+ "code": "UNCERTAINTY_NOT_REPORTED",
+ "severity": "minor",
+ "message": "The packet reports an effect size without an uncertainty interval.",
+ "evidence": {
+ "effectSize": "0.31 SD",
+ "confidenceInterval": null
+ },
+ "remediation": "Add confidence, credible, or bootstrap intervals and specify the estimation method."
+ }
+ ],
+ "reviewerNote": "education: HOLD before release because the manuscript uses causal language, but the stated design does not independently support causal inference."
+ },
+ {
+ "id": "ecology-longitudinal-003",
+ "title": "Wetland restoration is associated with higher amphibian diversity",
+ "domain": "ecology",
+ "decision": "RELEASE",
+ "confidence": 0.95,
+ "causalLanguageDetected": false,
+ "findingCount": 0,
+ "findings": [],
+ "reviewerNote": "ecology: causal wording is aligned with the study design and evidence packet."
+ },
+ {
+ "id": "oncology-subgroup-004",
+ "title": "Dose timing prevents relapse in a small oncology subgroup",
+ "domain": "oncology",
+ "decision": "REVISE",
+ "confidence": 0.79,
+ "causalLanguageDetected": true,
+ "findingCount": 2,
+ "findings": [
+ {
+ "code": "UNCERTAINTY_NOT_REPORTED",
+ "severity": "minor",
+ "message": "The packet reports an effect size without an uncertainty interval.",
+ "evidence": {
+ "effectSize": "hazard ratio 0.72",
+ "confidenceInterval": null
+ },
+ "remediation": "Add confidence, credible, or bootstrap intervals and specify the estimation method."
+ },
+ {
+ "code": "UNDERPOWERED_SUBGROUP_CAUSAL_CLAIM",
+ "severity": "minor",
+ "message": "At least one subgroup claim uses causal language without a powered subgroup analysis.",
+ "evidence": {
+ "subgroups": [
+ {
+ "name": "biomarker-positive subgroup",
+ "claim": "dose timing prevents relapse",
+ "powered": false
+ }
+ ]
+ },
+ "remediation": "Mark subgroup findings exploratory unless power and multiplicity controls are documented."
+ }
+ ],
+ "reviewerNote": "oncology: REVISE before release because the packet reports an effect size without an uncertainty interval."
+ }
+ ]
+}
diff --git a/causal-claim-validity-guard/artifacts/causal-claim-review.md b/causal-claim-validity-guard/artifacts/causal-claim-review.md
new file mode 100644
index 00000000..87285146
--- /dev/null
+++ b/causal-claim-validity-guard/artifacts/causal-claim-review.md
@@ -0,0 +1,46 @@
+# Causal Claim Validity Review
+
+Generated: 2026-08-15T20:24:14.343Z
+
+## Decision Counts
+
+- RELEASE: 2
+- REVISE: 1
+- HOLD: 1
+
+## Packet Reviews
+
+### Remote coaching reduces systolic blood pressure in high-risk adults
+Decision: RELEASE
+Confidence: 0.95
+Reviewer note: clinical trials: causal wording is aligned with the study design and evidence packet.
+
+### Daily tutoring app usage improves mathematics scores
+Decision: HOLD
+Confidence: 0.15
+Reviewer note: education: HOLD before release because the manuscript uses causal language, but the stated design does not independently support causal inference.
+- CRITICAL DESIGN_CANNOT_SUPPORT_CAUSAL_LANGUAGE: The manuscript uses causal language, but the stated design does not independently support causal inference.
+ Remediation: Replace causal verbs with associational language or add a causal identification strategy with assumptions and diagnostics.
+- MAJOR MISSING_COMPARATOR: Causal review requires a comparator, control, counterfactual, or explicit quasi-experimental contrast.
+ Remediation: Add a comparator/control description and explain why it approximates the counterfactual.
+- MAJOR TEMPORAL_ORDER_GAP: The evidence packet does not show that exposure or intervention preceded the outcome.
+ Remediation: Document timing of exposure, intervention, measurement, and outcome windows.
+- MAJOR CONFOUNDER_STRATEGY_MISSING: No confounder strategy is described for a causal claim.
+ Remediation: Describe randomization, matching, adjustment, stratification, sensitivity analysis, or a defensible natural experiment.
+- MINOR UNCERTAINTY_NOT_REPORTED: The packet reports an effect size without an uncertainty interval.
+ Remediation: Add confidence, credible, or bootstrap intervals and specify the estimation method.
+
+### Wetland restoration is associated with higher amphibian diversity
+Decision: RELEASE
+Confidence: 0.95
+Reviewer note: ecology: causal wording is aligned with the study design and evidence packet.
+
+### Dose timing prevents relapse in a small oncology subgroup
+Decision: REVISE
+Confidence: 0.79
+Reviewer note: oncology: REVISE before release because the packet reports an effect size without an uncertainty interval.
+- MINOR UNCERTAINTY_NOT_REPORTED: The packet reports an effect size without an uncertainty interval.
+ Remediation: Add confidence, credible, or bootstrap intervals and specify the estimation method.
+- MINOR UNDERPOWERED_SUBGROUP_CAUSAL_CLAIM: At least one subgroup claim uses causal language without a powered subgroup analysis.
+ Remediation: Mark subgroup findings exploratory unless power and multiplicity controls are documented.
+
diff --git a/causal-claim-validity-guard/artifacts/causal-claim-summary.svg b/causal-claim-validity-guard/artifacts/causal-claim-summary.svg
new file mode 100644
index 00000000..3ea5aa3e
--- /dev/null
+++ b/causal-claim-validity-guard/artifacts/causal-claim-summary.svg
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/causal-claim-validity-guard/artifacts/demo.mp4 b/causal-claim-validity-guard/artifacts/demo.mp4
new file mode 100644
index 00000000..3e19b58d
Binary files /dev/null and b/causal-claim-validity-guard/artifacts/demo.mp4 differ
diff --git a/causal-claim-validity-guard/data/sample_review_packets.json b/causal-claim-validity-guard/data/sample_review_packets.json
new file mode 100644
index 00000000..d19271ca
--- /dev/null
+++ b/causal-claim-validity-guard/data/sample_review_packets.json
@@ -0,0 +1,85 @@
+[
+ {
+ "id": "cardio-rct-001",
+ "title": "Remote coaching reduces systolic blood pressure in high-risk adults",
+ "domain": "clinical trials",
+ "claim": {
+ "intent": "causal",
+ "text": "Remote coaching reduces systolic blood pressure after twelve weeks."
+ },
+ "evidence": {
+ "design": "randomized_controlled_trial",
+ "hasComparator": true,
+ "temporalOrder": true,
+ "confounderStrategy": "blocked randomization with prespecified covariate balance checks",
+ "effectSize": "-7.4 mmHg",
+ "confidenceInterval": "95% CI -10.1 to -4.9",
+ "subgroups": [
+ {
+ "name": "participants over 65",
+ "claim": "coaching may reduce systolic blood pressure",
+ "powered": true
+ }
+ ]
+ },
+ "conclusion": "Remote coaching reduces systolic blood pressure in the randomized study population."
+ },
+ {
+ "id": "education-cross-sectional-002",
+ "title": "Daily tutoring app usage improves mathematics scores",
+ "domain": "education",
+ "claim": {
+ "intent": "causal",
+ "text": "Daily tutoring app usage improves mathematics scores."
+ },
+ "evidence": {
+ "design": "cross_sectional",
+ "hasComparator": false,
+ "temporalOrder": false,
+ "effectSize": "0.31 SD"
+ },
+ "conclusion": "The tutoring app drives higher scores across schools."
+ },
+ {
+ "id": "ecology-longitudinal-003",
+ "title": "Wetland restoration is associated with higher amphibian diversity",
+ "domain": "ecology",
+ "claim": {
+ "intent": "associational",
+ "text": "Wetland restoration is associated with higher amphibian diversity."
+ },
+ "evidence": {
+ "design": "longitudinal_observational",
+ "hasComparator": true,
+ "temporalOrder": true,
+ "confounderStrategy": "matched watersheds and rainfall adjustment",
+ "effectSize": "18% higher species richness",
+ "confidenceInterval": "95% CI 6% to 29%"
+ },
+ "conclusion": "Wetland restoration is associated with higher observed amphibian diversity after adjustment."
+ },
+ {
+ "id": "oncology-subgroup-004",
+ "title": "Dose timing prevents relapse in a small oncology subgroup",
+ "domain": "oncology",
+ "claim": {
+ "intent": "causal",
+ "text": "Dose timing prevents relapse in the biomarker-positive subgroup."
+ },
+ "evidence": {
+ "design": "quasi_experimental",
+ "hasComparator": true,
+ "temporalOrder": true,
+ "confounderStrategy": "propensity score weighting",
+ "effectSize": "hazard ratio 0.72",
+ "subgroups": [
+ {
+ "name": "biomarker-positive subgroup",
+ "claim": "dose timing prevents relapse",
+ "powered": false
+ }
+ ]
+ },
+ "conclusion": "Dose timing prevents relapse for biomarker-positive patients."
+ }
+]
diff --git a/causal-claim-validity-guard/package.json b/causal-claim-validity-guard/package.json
new file mode 100644
index 00000000..90b5945f
--- /dev/null
+++ b/causal-claim-validity-guard/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "causal-claim-validity-guard",
+ "version": "1.0.0",
+ "description": "Dependency-free causal claim validity guard for AI research assistant review packets.",
+ "main": "src/index.js",
+ "scripts": {
+ "test": "node --test",
+ "demo": "node scripts/demo.js",
+ "video": "node scripts/render-demo-video.js",
+ "check": "node --check src/index.js && node --check scripts/demo.js"
+ },
+ "license": "MIT"
+}
diff --git a/causal-claim-validity-guard/scripts/demo.js b/causal-claim-validity-guard/scripts/demo.js
new file mode 100644
index 00000000..afedd27e
--- /dev/null
+++ b/causal-claim-validity-guard/scripts/demo.js
@@ -0,0 +1,77 @@
+"use strict";
+
+const fs = require("node:fs");
+const path = require("node:path");
+const { analyzePackets } = require("../src");
+
+const root = path.resolve(__dirname, "..");
+const packets = JSON.parse(fs.readFileSync(path.join(root, "data", "sample_review_packets.json"), "utf8"));
+const report = analyzePackets(packets);
+const outDir = path.join(root, "artifacts");
+
+fs.mkdirSync(outDir, { recursive: true });
+fs.writeFileSync(path.join(outDir, "causal-claim-review.json"), `${JSON.stringify(report, null, 2)}\n`);
+fs.writeFileSync(path.join(outDir, "causal-claim-review.md"), renderMarkdown(report));
+fs.writeFileSync(path.join(outDir, "causal-claim-summary.svg"), renderSvg(report));
+
+console.log(JSON.stringify({
+ packetCount: report.packetCount,
+ decisionCounts: report.decisionCounts,
+ artifacts: [
+ "artifacts/causal-claim-review.json",
+ "artifacts/causal-claim-review.md",
+ "artifacts/causal-claim-summary.svg"
+ ]
+}, null, 2));
+
+function renderMarkdown(report) {
+ const lines = [
+ "# Causal Claim Validity Review",
+ "",
+ `Generated: ${report.generatedAt}`,
+ "",
+ "## Decision Counts",
+ "",
+ `- RELEASE: ${report.decisionCounts.RELEASE}`,
+ `- REVISE: ${report.decisionCounts.REVISE}`,
+ `- HOLD: ${report.decisionCounts.HOLD}`,
+ "",
+ "## Packet Reviews",
+ ""
+ ];
+
+ for (const review of report.reviews) {
+ lines.push(`### ${review.title}`);
+ lines.push(`Decision: ${review.decision}`);
+ lines.push(`Confidence: ${review.confidence}`);
+ lines.push(`Reviewer note: ${review.reviewerNote}`);
+ for (const item of review.findings) {
+ lines.push(`- ${item.severity.toUpperCase()} ${item.code}: ${item.message}`);
+ lines.push(` Remediation: ${item.remediation}`);
+ }
+ lines.push("");
+ }
+
+ return `${lines.join("\n")}\n`;
+}
+
+function renderSvg(report) {
+ const hold = report.decisionCounts.HOLD;
+ const revise = report.decisionCounts.REVISE;
+ const release = report.decisionCounts.RELEASE;
+ return ``;
+}
+
+function bar(label, count, x, y, color) {
+ const width = 96 + count * 110;
+ return `${label}: ${count}
+
+ `;
+}
diff --git a/causal-claim-validity-guard/scripts/render-demo-video.js b/causal-claim-validity-guard/scripts/render-demo-video.js
new file mode 100644
index 00000000..630d3393
--- /dev/null
+++ b/causal-claim-validity-guard/scripts/render-demo-video.js
@@ -0,0 +1,52 @@
+"use strict";
+
+const path = require("node:path");
+const { spawnSync } = require("node:child_process");
+
+const root = path.resolve(__dirname, "..");
+const ffmpeg = path.resolve(
+ root,
+ "..",
+ "..",
+ "tool_downloads",
+ "video_tools",
+ "node_modules",
+ "ffmpeg-static",
+ "ffmpeg.exe"
+);
+const out = path.join(root, "artifacts", "demo.mp4");
+const font = "C\\:/Windows/Fonts/arial.ttf";
+
+const draw = [
+ `drawtext=fontfile=${font}:text='Causal Claim Validity Guard':x=64:y=56:fontsize=46:fontcolor=black`,
+ `drawtext=fontfile=${font}:text='AI peer-review gate for causal overclaiming':x=64:y=122:fontsize=25:fontcolor=0x34413b`,
+ `drawtext=fontfile=${font}:text='Synthetic packets analyzed 4':x=64:y=212:fontsize=34:fontcolor=black`,
+ `drawtext=fontfile=${font}:text='RELEASE 2 REVISE 1 HOLD 1':x=64:y=270:fontsize=38:fontcolor=0x245fd6`,
+ `drawtext=fontfile=${font}:text='Checks design support comparator evidence temporal order confounders uncertainty':x=64:y=360:fontsize=26:fontcolor=0x34413b`,
+ `drawtext=fontfile=${font}:text='Validation passed npm test plus demo plus syntax check':x=64:y=424:fontsize=26:fontcolor=0x34413b`,
+ `drawtext=fontfile=${font}:text='No external APIs credentials private manuscripts or live data':x=64:y=488:fontsize=26:fontcolor=0x34413b`
+].join(",");
+
+const result = spawnSync(ffmpeg, [
+ "-y",
+ "-f",
+ "lavfi",
+ "-i",
+ "color=c=0xf8faf7:s=1280x720:d=8:r=30",
+ "-vf",
+ draw,
+ "-c:v",
+ "libx264",
+ "-pix_fmt",
+ "yuv420p",
+ "-movflags",
+ "+faststart",
+ out
+], { encoding: "utf8" });
+
+if (result.status !== 0) {
+ process.stderr.write(result.stderr || result.stdout);
+ process.exit(result.status || 1);
+}
+
+console.log(JSON.stringify({ out, bytes: require("node:fs").statSync(out).size }, null, 2));
diff --git a/causal-claim-validity-guard/src/index.js b/causal-claim-validity-guard/src/index.js
new file mode 100644
index 00000000..6bba8742
--- /dev/null
+++ b/causal-claim-validity-guard/src/index.js
@@ -0,0 +1,195 @@
+"use strict";
+
+const CAUSAL_TERMS = [
+ "cause",
+ "causes",
+ "caused",
+ "causal",
+ "effect of",
+ "leads to",
+ "resulted in",
+ "prevents",
+ "reduces",
+ "increases",
+ "drives",
+ "improves",
+ "protects against"
+];
+
+const CAUSAL_DESIGNS = new Set([
+ "randomized_controlled_trial",
+ "quasi_experimental",
+ "instrumental_variable",
+ "regression_discontinuity",
+ "difference_in_differences",
+ "natural_experiment"
+]);
+
+function includesCausalLanguage(value) {
+ const text = String(value || "").toLowerCase();
+ return CAUSAL_TERMS.some((term) => text.includes(term));
+}
+
+function finding(code, severity, message, evidence, remediation) {
+ return { code, severity, message, evidence, remediation };
+}
+
+function severityWeight(severity) {
+ if (severity === "critical") return 5;
+ if (severity === "major") return 3;
+ return 1;
+}
+
+function decisionFromFindings(findings) {
+ const score = findings.reduce((total, item) => total + severityWeight(item.severity), 0);
+ const critical = findings.some((item) => item.severity === "critical");
+ const major = findings.filter((item) => item.severity === "major").length;
+ if (critical || major >= 2 || score >= 7) return "HOLD";
+ if (major >= 1 || score >= 2) return "REVISE";
+ return "RELEASE";
+}
+
+function confidenceFromFindings(findings) {
+ const penalty = findings.reduce((total, item) => total + severityWeight(item.severity), 0);
+ return Math.max(0.15, Number((0.95 - penalty * 0.08).toFixed(2)));
+}
+
+function normalizePacket(packet) {
+ return {
+ id: packet.id,
+ title: packet.title,
+ domain: packet.domain || "general science",
+ claim: packet.claim || {},
+ evidence: packet.evidence || {},
+ conclusion: packet.conclusion || "",
+ reviewerContext: packet.reviewerContext || {}
+ };
+}
+
+function analyzePacket(input) {
+ const packet = normalizePacket(input);
+ const claimText = packet.claim.text || "";
+ const conclusion = packet.conclusion || "";
+ const evidence = packet.evidence;
+ const design = evidence.design || "unspecified";
+ const causalIntent = packet.claim.intent === "causal" || includesCausalLanguage(claimText);
+ const conclusionCausal = includesCausalLanguage(conclusion);
+ const findings = [];
+
+ if (causalIntent && !CAUSAL_DESIGNS.has(design)) {
+ findings.push(finding(
+ "DESIGN_CANNOT_SUPPORT_CAUSAL_LANGUAGE",
+ design === "cross_sectional" || design === "case_series" ? "critical" : "major",
+ "The manuscript uses causal language, but the stated design does not independently support causal inference.",
+ { design, claim: claimText },
+ "Replace causal verbs with associational language or add a causal identification strategy with assumptions and diagnostics."
+ ));
+ }
+
+ if ((causalIntent || conclusionCausal) && evidence.hasComparator !== true) {
+ findings.push(finding(
+ "MISSING_COMPARATOR",
+ "major",
+ "Causal review requires a comparator, control, counterfactual, or explicit quasi-experimental contrast.",
+ { hasComparator: evidence.hasComparator },
+ "Add a comparator/control description and explain why it approximates the counterfactual."
+ ));
+ }
+
+ if ((causalIntent || conclusionCausal) && evidence.temporalOrder !== true) {
+ findings.push(finding(
+ "TEMPORAL_ORDER_GAP",
+ "major",
+ "The evidence packet does not show that exposure or intervention preceded the outcome.",
+ { temporalOrder: evidence.temporalOrder },
+ "Document timing of exposure, intervention, measurement, and outcome windows."
+ ));
+ }
+
+ if ((causalIntent || conclusionCausal) && !evidence.confounderStrategy) {
+ findings.push(finding(
+ "CONFOUNDER_STRATEGY_MISSING",
+ "major",
+ "No confounder strategy is described for a causal claim.",
+ { confounderStrategy: evidence.confounderStrategy || null },
+ "Describe randomization, matching, adjustment, stratification, sensitivity analysis, or a defensible natural experiment."
+ ));
+ }
+
+ if ((causalIntent || conclusionCausal) && evidence.effectSize && !evidence.confidenceInterval) {
+ findings.push(finding(
+ "UNCERTAINTY_NOT_REPORTED",
+ "minor",
+ "The packet reports an effect size without an uncertainty interval.",
+ { effectSize: evidence.effectSize, confidenceInterval: evidence.confidenceInterval || null },
+ "Add confidence, credible, or bootstrap intervals and specify the estimation method."
+ ));
+ }
+
+ if (packet.claim.intent === "associational" && conclusionCausal) {
+ findings.push(finding(
+ "CONCLUSION_OVERSTATES_ASSOCIATION",
+ "major",
+ "The claim is labeled associational but the conclusion uses causal wording.",
+ { intent: packet.claim.intent, conclusion },
+ "Align the conclusion with the claim intent or provide a causal design and evidence upgrade."
+ ));
+ }
+
+ if (Array.isArray(evidence.subgroups) && evidence.subgroups.some((group) => group.powered !== true && includesCausalLanguage(group.claim))) {
+ findings.push(finding(
+ "UNDERPOWERED_SUBGROUP_CAUSAL_CLAIM",
+ "minor",
+ "At least one subgroup claim uses causal language without a powered subgroup analysis.",
+ { subgroups: evidence.subgroups },
+ "Mark subgroup findings exploratory unless power and multiplicity controls are documented."
+ ));
+ }
+
+ const decision = decisionFromFindings(findings);
+ return {
+ id: packet.id,
+ title: packet.title,
+ domain: packet.domain,
+ decision,
+ confidence: confidenceFromFindings(findings),
+ causalLanguageDetected: causalIntent || conclusionCausal,
+ findingCount: findings.length,
+ findings,
+ reviewerNote: reviewerNote(packet, decision, findings)
+ };
+}
+
+function reviewerNote(packet, decision, findings) {
+ if (decision === "RELEASE") {
+ return `${packet.domain}: causal wording is aligned with the study design and evidence packet.`;
+ }
+ const first = findings[0];
+ return `${packet.domain}: ${decision} before release because ${first.message.toLowerCase()}`;
+}
+
+function analyzePackets(packets) {
+ const reviews = packets.map(analyzePacket);
+ const counts = reviews.reduce((acc, review) => {
+ acc[review.decision] = (acc[review.decision] || 0) + 1;
+ return acc;
+ }, {});
+ return {
+ generatedAt: new Date().toISOString(),
+ packetCount: reviews.length,
+ decisionCounts: {
+ RELEASE: counts.RELEASE || 0,
+ REVISE: counts.REVISE || 0,
+ HOLD: counts.HOLD || 0
+ },
+ reviews
+ };
+}
+
+module.exports = {
+ CAUSAL_TERMS,
+ CAUSAL_DESIGNS,
+ analyzePacket,
+ analyzePackets,
+ includesCausalLanguage
+};
diff --git a/causal-claim-validity-guard/test/causal-claim-validity-guard.test.js b/causal-claim-validity-guard/test/causal-claim-validity-guard.test.js
new file mode 100644
index 00000000..012e5f89
--- /dev/null
+++ b/causal-claim-validity-guard/test/causal-claim-validity-guard.test.js
@@ -0,0 +1,80 @@
+"use strict";
+
+const assert = require("node:assert/strict");
+const test = require("node:test");
+const { analyzePacket, analyzePackets, includesCausalLanguage } = require("../src");
+
+test("detects causal language in common manuscript wording", () => {
+ assert.equal(includesCausalLanguage("The intervention reduces risk."), true);
+ assert.equal(includesCausalLanguage("The marker is associated with risk."), false);
+});
+
+test("holds causal claims from cross-sectional evidence without comparator or temporal order", () => {
+ const review = analyzePacket({
+ id: "bad-causal",
+ title: "App usage improves outcomes",
+ claim: {
+ intent: "causal",
+ text: "The app improves outcomes."
+ },
+ evidence: {
+ design: "cross_sectional",
+ hasComparator: false,
+ temporalOrder: false,
+ effectSize: "0.4 SD"
+ },
+ conclusion: "The app causes better outcomes."
+ });
+
+ assert.equal(review.decision, "HOLD");
+ assert.ok(review.findings.some((item) => item.code === "DESIGN_CANNOT_SUPPORT_CAUSAL_LANGUAGE"));
+ assert.ok(review.findings.some((item) => item.code === "MISSING_COMPARATOR"));
+ assert.ok(review.findings.some((item) => item.code === "TEMPORAL_ORDER_GAP"));
+});
+
+test("releases supported randomized causal claims", () => {
+ const review = analyzePacket({
+ id: "good-rct",
+ title: "Coaching reduces blood pressure",
+ domain: "clinical trials",
+ claim: {
+ intent: "causal",
+ text: "Coaching reduces blood pressure."
+ },
+ evidence: {
+ design: "randomized_controlled_trial",
+ hasComparator: true,
+ temporalOrder: true,
+ confounderStrategy: "blocked randomization",
+ effectSize: "-7 mmHg",
+ confidenceInterval: "95% CI -10 to -4"
+ },
+ conclusion: "Coaching reduces blood pressure in this randomized trial."
+ });
+
+ assert.equal(review.decision, "RELEASE");
+ assert.equal(review.findingCount, 0);
+});
+
+test("summarizes packet decisions deterministically", () => {
+ const report = analyzePackets([
+ {
+ id: "a",
+ title: "A",
+ claim: { intent: "associational", text: "A is associated with B." },
+ evidence: { design: "cross_sectional" },
+ conclusion: "A is associated with B."
+ },
+ {
+ id: "b",
+ title: "B",
+ claim: { intent: "causal", text: "A prevents B." },
+ evidence: { design: "case_series", hasComparator: false, temporalOrder: false },
+ conclusion: "A prevents B."
+ }
+ ]);
+
+ assert.equal(report.packetCount, 2);
+ assert.equal(report.decisionCounts.RELEASE, 1);
+ assert.equal(report.decisionCounts.HOLD, 1);
+});