jupytermind 0.3.0

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 (193) hide show
  1. package/.github/skills/ai-chemistry-scientist/SKILL.md +97 -0
  2. package/.github/skills/ai-chemistry-scientist/manifest.json +156 -0
  3. package/.github/skills/ai-data-scientist/SKILL.md +330 -0
  4. package/.github/skills/ai-genomics-scientist/SKILL.md +98 -0
  5. package/.github/skills/ai-genomics-scientist/manifest.json +93 -0
  6. package/.github/skills/ai-materials-scientist/SKILL.md +51 -0
  7. package/.github/skills/ai-materials-scientist/manifest.json +58 -0
  8. package/.github/skills/ai-scientist/SKILL.md +69 -0
  9. package/.github/skills/ai-scientist/manifest.json +61 -0
  10. package/.github/skills/ai-structural-biology-scientist/SKILL.md +67 -0
  11. package/.github/skills/ai-structural-biology-scientist/manifest.json +72 -0
  12. package/.github/skills/japanese-prose/NOTICE.md +17 -0
  13. package/.github/skills/japanese-prose/SKILL.md +111 -0
  14. package/.github/skills/japanese-prose/references/review-workflow.md +50 -0
  15. package/.github/skills/japanese-prose/references/scoring.md +24 -0
  16. package/.github/skills/japanese-prose/references/writing-guidelines.md +60 -0
  17. package/.github/skills/japanese-prose/scripts/core.py +192 -0
  18. package/.github/skills/japanese-prose/scripts/fixtures/natural.md +5 -0
  19. package/.github/skills/japanese-prose/scripts/fixtures/unnatural.md +5 -0
  20. package/.github/skills/japanese-prose/scripts/lint.py +378 -0
  21. package/.github/skills/japanese-prose/scripts/outline.py +68 -0
  22. package/.github/skills/japanese-prose/scripts/terms.py +112 -0
  23. package/.github/skills/japanese-prose/scripts/test_engine.py +117 -0
  24. package/.github/skills/presentation-planner/SKILL.md +257 -0
  25. package/.github/skills/presentation-planner/assets/design-templates/data-report.yaml +97 -0
  26. package/.github/skills/presentation-planner/assets/design-templates/executive-proposal.yaml +92 -0
  27. package/.github/skills/presentation-planner/assets/design-templates/technical-briefing.yaml +96 -0
  28. package/.github/skills/presentation-planner/assets/scenario-templates/data-report.md +47 -0
  29. package/.github/skills/presentation-planner/assets/scenario-templates/executive-decision.md +43 -0
  30. package/.github/skills/presentation-planner/assets/scenario-templates/technical-briefing.md +45 -0
  31. package/.github/skills/presentation-planner/references/customizing-design-templates.md +160 -0
  32. package/.github/skills/presentation-planner/references/design-spec-schema.md +72 -0
  33. package/.github/skills/presentation-planner/references/handoff-contract.md +49 -0
  34. package/.github/skills/presentation-planner/references/responsibility-boundary.md +32 -0
  35. package/.github/skills/presentation-planner/references/scenario-templates.md +55 -0
  36. package/.github/skills/tech-writer/SKILL.md +434 -0
  37. package/.github/skills/tech-writer/assets/templates/blueprint.md +187 -0
  38. package/.github/skills/tech-writer/assets/templates/design-doc.md +29 -0
  39. package/.github/skills/tech-writer/assets/templates/migration-plan.md +173 -0
  40. package/.github/skills/tech-writer/assets/templates/operations-runbook.md +202 -0
  41. package/.github/skills/tech-writer/assets/templates/pr-description.md +23 -0
  42. package/.github/skills/tech-writer/assets/templates/qiita.md +44 -0
  43. package/.github/skills/tech-writer/assets/templates/readme.md +38 -0
  44. package/.github/skills/tech-writer/assets/templates/requirements-definition.md +170 -0
  45. package/.github/skills/tech-writer/assets/templates/rfi.md +113 -0
  46. package/.github/skills/tech-writer/assets/templates/rfp.md +180 -0
  47. package/.github/skills/tech-writer/assets/templates/security-design.md +167 -0
  48. package/.github/skills/tech-writer/assets/templates/system-design.md +220 -0
  49. package/.github/skills/tech-writer/assets/templates/technical-proposal.md +112 -0
  50. package/.github/skills/tech-writer/assets/templates/test-plan.md +153 -0
  51. package/.github/skills/tech-writer/assets/templates/user-manual.md +22 -0
  52. package/.github/skills/tech-writer/assets/templates/white-paper.md +192 -0
  53. package/.github/skills/tech-writer/references/doctypes/api-docs.md +33 -0
  54. package/.github/skills/tech-writer/references/doctypes/blueprint.md +81 -0
  55. package/.github/skills/tech-writer/references/doctypes/code-comments.md +39 -0
  56. package/.github/skills/tech-writer/references/doctypes/design-doc.md +42 -0
  57. package/.github/skills/tech-writer/references/doctypes/migration-plan.md +63 -0
  58. package/.github/skills/tech-writer/references/doctypes/operations-runbook.md +63 -0
  59. package/.github/skills/tech-writer/references/doctypes/pr-commit.md +82 -0
  60. package/.github/skills/tech-writer/references/doctypes/qiita.md +75 -0
  61. package/.github/skills/tech-writer/references/doctypes/readme.md +43 -0
  62. package/.github/skills/tech-writer/references/doctypes/release-notes.md +30 -0
  63. package/.github/skills/tech-writer/references/doctypes/requirements-definition.md +61 -0
  64. package/.github/skills/tech-writer/references/doctypes/rfi.md +43 -0
  65. package/.github/skills/tech-writer/references/doctypes/rfp.md +46 -0
  66. package/.github/skills/tech-writer/references/doctypes/security-design.md +71 -0
  67. package/.github/skills/tech-writer/references/doctypes/system-design.md +74 -0
  68. package/.github/skills/tech-writer/references/doctypes/technical-proposal.md +49 -0
  69. package/.github/skills/tech-writer/references/doctypes/test-plan.md +67 -0
  70. package/.github/skills/tech-writer/references/doctypes/user-manual.md +58 -0
  71. package/.github/skills/tech-writer/references/doctypes/white-paper.md +84 -0
  72. package/.github/skills/tech-writer/references/doctypes/zenn.md +66 -0
  73. package/.github/skills/tech-writer/references/japanese-prose-optimization.md +110 -0
  74. package/.github/skills/tech-writer/references/style-constitution.md +104 -0
  75. package/.github/skills/tech-writer/scripts/lint.py +412 -0
  76. package/LICENSE +21 -0
  77. package/README.md +92 -0
  78. package/bin/ai-data-scientist.js +123 -0
  79. package/package.json +41 -0
  80. package/pyproject.toml +45 -0
  81. package/src/ai_chemistry_scientist/__init__.py +0 -0
  82. package/src/ai_chemistry_scientist/admet_prediction.py +71 -0
  83. package/src/ai_chemistry_scientist/bioactivity_classification.py +73 -0
  84. package/src/ai_chemistry_scientist/data/sample_molecules.csv +21 -0
  85. package/src/ai_chemistry_scientist/dispatch.py +369 -0
  86. package/src/ai_chemistry_scientist/docking_score.py +97 -0
  87. package/src/ai_chemistry_scientist/drug_likeness_rules.py +84 -0
  88. package/src/ai_chemistry_scientist/evidence.py +41 -0
  89. package/src/ai_chemistry_scientist/molecular_descriptors.py +97 -0
  90. package/src/ai_chemistry_scientist/molecular_formula_mass.py +40 -0
  91. package/src/ai_chemistry_scientist/molecular_similarity.py +78 -0
  92. package/src/ai_chemistry_scientist/qsar_modeling.py +105 -0
  93. package/src/ai_chemistry_scientist/salt_standardization.py +81 -0
  94. package/src/ai_chemistry_scientist/structural_alerts.py +76 -0
  95. package/src/ai_chemistry_scientist/structure_format_conversion.py +84 -0
  96. package/src/ai_chemistry_scientist/validation.py +70 -0
  97. package/src/ai_data_scientist/__init__.py +0 -0
  98. package/src/ai_data_scientist/analysis_assumptions.py +121 -0
  99. package/src/ai_data_scientist/anomaly_detection.py +39 -0
  100. package/src/ai_data_scientist/automl.py +109 -0
  101. package/src/ai_data_scientist/cleaning.py +56 -0
  102. package/src/ai_data_scientist/cli.py +90 -0
  103. package/src/ai_data_scientist/clustering.py +54 -0
  104. package/src/ai_data_scientist/dashboard.py +33 -0
  105. package/src/ai_data_scientist/data_definition.py +100 -0
  106. package/src/ai_data_scientist/data_quality.py +164 -0
  107. package/src/ai_data_scientist/dataset_validation.py +135 -0
  108. package/src/ai_data_scientist/dependency_pins.py +60 -0
  109. package/src/ai_data_scientist/eda.py +82 -0
  110. package/src/ai_data_scientist/experiment_evaluation.py +635 -0
  111. package/src/ai_data_scientist/explainability.py +340 -0
  112. package/src/ai_data_scientist/feature_engineering.py +163 -0
  113. package/src/ai_data_scientist/gate_config.py +32 -0
  114. package/src/ai_data_scientist/ingestion.py +127 -0
  115. package/src/ai_data_scientist/insight_engine.py +180 -0
  116. package/src/ai_data_scientist/japanese_nlp.py +43 -0
  117. package/src/ai_data_scientist/jupyter_launcher.py +137 -0
  118. package/src/ai_data_scientist/jupyter_mcp_client.py +94 -0
  119. package/src/ai_data_scientist/language_router.py +28 -0
  120. package/src/ai_data_scientist/lifecycle.py +221 -0
  121. package/src/ai_data_scientist/mcp_gateway.py +113 -0
  122. package/src/ai_data_scientist/mcp_runtime.py +194 -0
  123. package/src/ai_data_scientist/mcp_transport.py +53 -0
  124. package/src/ai_data_scientist/ml_modeling.py +451 -0
  125. package/src/ai_data_scientist/model_tuning.py +104 -0
  126. package/src/ai_data_scientist/notebook_audit.py +574 -0
  127. package/src/ai_data_scientist/project_manager.py +243 -0
  128. package/src/ai_data_scientist/report_export.py +73 -0
  129. package/src/ai_data_scientist/sensitivity.py +445 -0
  130. package/src/ai_data_scientist/signal_analysis.py +201 -0
  131. package/src/ai_data_scientist/skill_packaging.py +40 -0
  132. package/src/ai_data_scientist/stats_analysis.py +88 -0
  133. package/src/ai_data_scientist/text_nlp.py +44 -0
  134. package/src/ai_data_scientist/timeseries.py +68 -0
  135. package/src/ai_data_scientist/visualization.py +708 -0
  136. package/src/ai_genomics_scientist/__init__.py +1 -0
  137. package/src/ai_genomics_scientist/differential_expression.py +147 -0
  138. package/src/ai_genomics_scientist/dispatch.py +267 -0
  139. package/src/ai_genomics_scientist/evidence.py +45 -0
  140. package/src/ai_genomics_scientist/gene_set_enrichment.py +76 -0
  141. package/src/ai_genomics_scientist/sequence_alignment.py +97 -0
  142. package/src/ai_genomics_scientist/sequence_features.py +111 -0
  143. package/src/ai_genomics_scientist/splice_site_scoring.py +66 -0
  144. package/src/ai_genomics_scientist/validation.py +83 -0
  145. package/src/ai_genomics_scientist/variant_effect.py +147 -0
  146. package/src/ai_genomics_scientist/variant_pathogenicity.py +125 -0
  147. package/src/ai_materials_scientist/__init__.py +0 -0
  148. package/src/ai_materials_scientist/calphad.py +117 -0
  149. package/src/ai_materials_scientist/classical_monte_carlo.py +165 -0
  150. package/src/ai_materials_scientist/crystal_plasticity.py +184 -0
  151. package/src/ai_materials_scientist/dispatch.py +100 -0
  152. package/src/ai_materials_scientist/evidence.py +84 -0
  153. package/src/ai_materials_scientist/fem.py +279 -0
  154. package/src/ai_materials_scientist/kinetic_monte_carlo.py +145 -0
  155. package/src/ai_materials_scientist/molecular_dynamics.py +240 -0
  156. package/src/ai_materials_scientist/phase_field.py +167 -0
  157. package/src/ai_materials_scientist/validation.py +70 -0
  158. package/src/ai_scientist/__init__.py +1 -0
  159. package/src/ai_scientist/completion_gate.py +15 -0
  160. package/src/ai_scientist/data_analysis.py +46 -0
  161. package/src/ai_scientist/evidence_registry.py +99 -0
  162. package/src/ai_scientist/experimental_design.py +20 -0
  163. package/src/ai_scientist/language.py +14 -0
  164. package/src/ai_scientist/latex_renderer.py +41 -0
  165. package/src/ai_scientist/literature_review.py +37 -0
  166. package/src/ai_scientist/manifest.py +87 -0
  167. package/src/ai_scientist/manuscript.py +94 -0
  168. package/src/ai_scientist/mcp_config.py +76 -0
  169. package/src/ai_scientist/mcp_external.py +42 -0
  170. package/src/ai_scientist/mcp_failures.py +23 -0
  171. package/src/ai_scientist/mcp_gateway.py +38 -0
  172. package/src/ai_scientist/mcp_managed.py +180 -0
  173. package/src/ai_scientist/npm_packaging.py +49 -0
  174. package/src/ai_scientist/orchestrator.py +133 -0
  175. package/src/ai_scientist/peer_review.py +60 -0
  176. package/src/ai_scientist/phase_gate.py +74 -0
  177. package/src/ai_scientist/phase_state.py +230 -0
  178. package/src/ai_scientist/presentation.py +56 -0
  179. package/src/ai_scientist/project_config.py +31 -0
  180. package/src/ai_scientist/project_handle.py +74 -0
  181. package/src/ai_scientist/reproducibility.py +20 -0
  182. package/src/ai_scientist/research_planning.py +20 -0
  183. package/src/ai_scientist/skill_invocation.py +21 -0
  184. package/src/ai_scientist/tdd_gate.py +99 -0
  185. package/src/ai_structural_biology_scientist/__init__.py +0 -0
  186. package/src/ai_structural_biology_scientist/contact_map.py +87 -0
  187. package/src/ai_structural_biology_scientist/dispatch.py +269 -0
  188. package/src/ai_structural_biology_scientist/evidence.py +43 -0
  189. package/src/ai_structural_biology_scientist/hydrophobicity.py +101 -0
  190. package/src/ai_structural_biology_scientist/protein_docking_score.py +104 -0
  191. package/src/ai_structural_biology_scientist/secondary_structure.py +95 -0
  192. package/src/ai_structural_biology_scientist/structural_similarity.py +74 -0
  193. package/src/ai_structural_biology_scientist/validation.py +100 -0
@@ -0,0 +1,167 @@
1
+ """Phase-field microstructure evolution module (DES-AIMS-010 / REQ-AIMS-010)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+
7
+ from ai_materials_scientist.evidence import record_run
8
+ from ai_materials_scientist.validation import (
9
+ check_finite_array,
10
+ check_positive_output_interval,
11
+ check_positive_step_count,
12
+ register_validator,
13
+ validate_parameters,
14
+ )
15
+
16
+ _MODULE_NAME = "phase-field"
17
+ _MODELS = ("allen-cahn", "cahn-hilliard")
18
+
19
+
20
+ def _laplacian(c: np.ndarray, dx: float) -> np.ndarray:
21
+ """Periodic five-point discrete Laplacian L(c) (REQ-AIMS-010)."""
22
+ return (
23
+ np.roll(c, -1, axis=0)
24
+ + np.roll(c, 1, axis=0)
25
+ + np.roll(c, -1, axis=1)
26
+ + np.roll(c, 1, axis=1)
27
+ - 4 * c
28
+ ) / (dx**2)
29
+
30
+
31
+ def _f_prime(c: np.ndarray) -> np.ndarray:
32
+ """Derivative of f(c) = c^2 * (1-c)^2."""
33
+ return 2 * c * (1 - c) ** 2 - 2 * (c**2) * (1 - c)
34
+
35
+
36
+ # @id CODE-AIMS-010
37
+ # @implements REQ-AIMS-010
38
+ # @design DES-AIMS-010
39
+ def compute_dt_bound(model: str, M: float, kappa: float, dx: float) -> float:
40
+ """Return the model-specific explicit stability time-step bound."""
41
+ lambda_max = 8.0 / (dx**2)
42
+ if model == "allen-cahn":
43
+ return 2.0 / (M * (2 + kappa * lambda_max))
44
+ if model == "cahn-hilliard":
45
+ return 2.0 / (M * lambda_max * (2 + kappa * lambda_max))
46
+ raise ValueError(f"model must be one of {_MODELS}, got {model!r}")
47
+
48
+
49
+ def _phase_field_validator(params: dict) -> dict:
50
+ """DES-AIMS-002 registered validator for module_name='phase-field'."""
51
+ model = params["model"]
52
+ M = params["M"]
53
+ kappa = params["kappa"]
54
+ dx = params["dx"]
55
+ dt = params["dt"]
56
+
57
+ if model not in _MODELS:
58
+ return {"ok": False, "parameter": "model", "constraint": f"model in {_MODELS}"}
59
+ if M <= 0:
60
+ return {"ok": False, "parameter": "M", "constraint": "M > 0"}
61
+ if kappa < 0:
62
+ return {"ok": False, "parameter": "kappa", "constraint": "kappa >= 0"}
63
+ if dx <= 0:
64
+ return {"ok": False, "parameter": "dx", "constraint": "dx > 0"}
65
+ if dt <= 0:
66
+ return {"ok": False, "parameter": "dt", "constraint": "dt > 0"}
67
+
68
+ finite_check = check_finite_array("field0", params["field0"])
69
+ if not finite_check["ok"]:
70
+ return finite_check
71
+ steps_check = check_positive_step_count(params["steps"])
72
+ if not steps_check["ok"]:
73
+ return steps_check
74
+ interval_check = check_positive_output_interval(params["output_every"])
75
+ if not interval_check["ok"]:
76
+ return interval_check
77
+
78
+ dt_bound = compute_dt_bound(model, M, kappa, dx)
79
+ if dt > dt_bound:
80
+ return {
81
+ "parameter": "dt",
82
+ "ok": False,
83
+ "constraint": f"dt <= dt_bound ({dt_bound}) for model {model!r}",
84
+ }
85
+ return {"ok": True}
86
+
87
+
88
+ register_validator(_MODULE_NAME, _phase_field_validator)
89
+
90
+
91
+ def _validate(field0, model, dx, M, kappa, dt, steps, output_every) -> None:
92
+ """Route through the shared DES-AIMS-002 registry (REQ-AIMS-003)."""
93
+ result = validate_parameters(
94
+ _MODULE_NAME,
95
+ {
96
+ "field0": field0,
97
+ "model": model,
98
+ "dx": dx,
99
+ "M": M,
100
+ "kappa": kappa,
101
+ "dt": dt,
102
+ "steps": steps,
103
+ "output_every": output_every,
104
+ },
105
+ )
106
+ if not result["ok"]:
107
+ raise ValueError(f"{result['parameter']}: {result['constraint']}")
108
+
109
+
110
+ # @id CODE-AIMS-911
111
+ # @implements REQ-AIMS-010 REQ-AIMS-003
112
+ # @design DES-AIMS-010
113
+ def run_phase_field(
114
+ field0: np.ndarray,
115
+ model: str,
116
+ dx: float,
117
+ M: float,
118
+ kappa: float,
119
+ dt: float,
120
+ steps: int,
121
+ output_every: int,
122
+ ) -> dict:
123
+ """Integrate the Allen-Cahn or Cahn-Hilliard PDE and report snapshots.
124
+
125
+ Rejects any out-of-domain parameter (REQ-AIMS-003) before performing any
126
+ integration step or mutating ``field0``.
127
+ """
128
+ _validate(field0, model, dx, M, kappa, dt, steps, output_every)
129
+
130
+ c = field0.astype(np.float64).copy()
131
+ snapshots = [c.copy()]
132
+ times = [0.0]
133
+
134
+ for step in range(1, steps + 1):
135
+ mu = _f_prime(c) - kappa * _laplacian(c, dx)
136
+ if model == "allen-cahn":
137
+ c = c - dt * M * mu
138
+ else: # cahn-hilliard
139
+ c = c + dt * M * _laplacian(mu, dx)
140
+ if step % output_every == 0:
141
+ snapshots.append(c.copy())
142
+ times.append(step * dt)
143
+
144
+ dt_bound = compute_dt_bound(model, M, kappa, dx)
145
+ return {
146
+ "snapshots": snapshots,
147
+ "times": times,
148
+ "dt_bound": dt_bound,
149
+ }
150
+
151
+
152
+ # @id CODE-AIMS-912
153
+ # @implements REQ-AIMS-010 REQ-AIMS-004 REQ-AIMS-005
154
+ # @design DES-AIMS-010
155
+ def run_phase_field_with_evidence(**kwargs) -> dict:
156
+ """Run phase-field and wrap the result as a reproducible RunRecord."""
157
+ result = run_phase_field(**kwargs)
158
+ return record_run(
159
+ module_name="phase-field",
160
+ unit_system="dimensionless-order-parameter",
161
+ params={k: v for k, v in kwargs.items() if k != "field0"},
162
+ arrays={
163
+ "snapshots": np.stack(result["snapshots"]),
164
+ "times": np.array(result["times"], dtype=np.float64),
165
+ },
166
+ seed=None,
167
+ )
@@ -0,0 +1,70 @@
1
+ """Shared parameter & stability validator (DES-AIMS-002 / REQ-AIMS-003)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import Any
7
+
8
+ import numpy as np
9
+
10
+ ValidationResult = dict
11
+ ValidatorFn = Callable[[dict[str, Any]], ValidationResult]
12
+
13
+ _REGISTRY: dict[str, ValidatorFn] = {}
14
+
15
+
16
+ def _ok() -> ValidationResult:
17
+ return {"ok": True}
18
+
19
+
20
+ def _fail(parameter: str, constraint: str) -> ValidationResult:
21
+ return {"ok": False, "parameter": parameter, "constraint": constraint}
22
+
23
+
24
+ # @id CODE-AIMS-002
25
+ # @implements REQ-AIMS-003
26
+ # @design DES-AIMS-002
27
+ def check_finite_array(parameter: str, array: np.ndarray) -> ValidationResult:
28
+ """Reject a numeric array containing any non-finite (NaN/Inf) value."""
29
+ if not np.all(np.isfinite(array)):
30
+ return _fail(parameter, "all elements must be finite (no NaN/Inf)")
31
+ return _ok()
32
+
33
+
34
+ # @id CODE-AIMS-913
35
+ # @implements REQ-AIMS-003
36
+ # @design DES-AIMS-002
37
+ def check_positive_step_count(steps: int) -> ValidationResult:
38
+ """Reject a step count that is not a positive integer."""
39
+ if steps <= 0:
40
+ return _fail("steps", "steps > 0")
41
+ return _ok()
42
+
43
+
44
+ # @id CODE-AIMS-914
45
+ # @implements REQ-AIMS-003
46
+ # @design DES-AIMS-002
47
+ def check_positive_output_interval(output_every: int) -> ValidationResult:
48
+ """Reject an output interval that is not a positive integer."""
49
+ if output_every <= 0:
50
+ return _fail("output_every", "output_every > 0")
51
+ return _ok()
52
+
53
+
54
+ # @id CODE-AIMS-915
55
+ # @implements REQ-AIMS-003
56
+ # @design DES-AIMS-002
57
+ def register_validator(module_name: str, validator: ValidatorFn) -> None:
58
+ """Register ``module_name``'s own documented-domain validator function."""
59
+ _REGISTRY[module_name] = validator
60
+
61
+
62
+ # @id CODE-AIMS-916
63
+ # @implements REQ-AIMS-003
64
+ # @design DES-AIMS-002
65
+ def validate_parameters(module_name: str, params: dict[str, Any]) -> ValidationResult:
66
+ """Dispatch to ``module_name``'s registered validator before any simulation step."""
67
+ validator = _REGISTRY.get(module_name)
68
+ if validator is None:
69
+ return _fail("module", f"no validator registered for module '{module_name}'")
70
+ return validator(params)
@@ -0,0 +1 @@
1
+ """AI Scientist research workflow package."""
@@ -0,0 +1,15 @@
1
+ """Evidence-based completion validation."""
2
+
3
+ from ai_scientist.evidence_registry import query_evidence
4
+ from ai_scientist.project_handle import ResearchProjectHandle
5
+
6
+
7
+ # @id CODE-AISCI-021
8
+ # @implements REQ-AISCI-020
9
+ # @design DES-AISCI-017
10
+ def validate_completion_evidence(handle: ResearchProjectHandle, phase: str) -> bool:
11
+ """Require at least one correctly attributed evidence record for ``phase``."""
12
+ return any(
13
+ record.project == handle.name and record.phase == phase and bool(record.created_at)
14
+ for record in query_evidence(handle, phase)
15
+ )
@@ -0,0 +1,46 @@
1
+ """Data-analysis delegation to ai-data-scientist."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+
7
+ import ai_data_scientist.project_manager as aids_project_manager
8
+ from ai_data_scientist.mcp_gateway import run_and_record
9
+
10
+ from ai_scientist.evidence_registry import record_evidence
11
+ from ai_scientist.project_handle import ResearchProjectHandle
12
+
13
+
14
+ def _now() -> str:
15
+ return datetime.now(timezone.utc).isoformat()
16
+
17
+
18
+ # @id CODE-AISCI-009
19
+ # @implements REQ-AISCI-008
20
+ # @design DES-AISCI-005
21
+ def delegate_data_analysis(
22
+ handle: ResearchProjectHandle,
23
+ instruction: str,
24
+ mcp_client=None,
25
+ ) -> dict:
26
+ """Delegate notebook setup and optional execution to ai-data-scientist."""
27
+ if mcp_client is None:
28
+ raise ValueError("Data-analysis requires an MCP client for ai-data-scientist execution.")
29
+ aids_handle = aids_project_manager.resolve_project(handle.name)
30
+ if aids_handle.root != handle.root or aids_handle.notebook_path != handle.notebook_path:
31
+ raise ValueError("ai-data-scientist handle diverged from ai-scientist project handle.")
32
+ notebook_path = aids_project_manager.ensure_notebook(aids_handle)
33
+ execution = run_and_record(mcp_client, aids_handle, instruction)
34
+ record_evidence(
35
+ handle,
36
+ "data-analysis",
37
+ notebook_path,
38
+ "notebook",
39
+ _now(),
40
+ metadata={"executionRoutedVia": "ai_data_scientist.mcp_gateway"},
41
+ )
42
+ return {
43
+ "phase": "data-analysis",
44
+ "notebook_path": str(notebook_path),
45
+ "execution": execution,
46
+ }
@@ -0,0 +1,99 @@
1
+ """Durable evidence registry for ai_scientist phases."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import threading
7
+ from dataclasses import asdict, dataclass, field
8
+ from pathlib import Path
9
+
10
+ from ai_scientist.project_handle import ResearchProjectHandle
11
+
12
+ DEFAULT_EVIDENCE_PATH = ".ai_scientist_evidence.json"
13
+ _REGISTRY_LOCKS: dict[Path, threading.Lock] = {}
14
+ _REGISTRY_LOCKS_GUARD = threading.Lock()
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class EvidenceRecord:
19
+ """Recorded project evidence with phase attribution."""
20
+
21
+ project: str
22
+ phase: str
23
+ artifact_path: str
24
+ artifact_kind: str
25
+ created_at: str
26
+ metadata: dict[str, str] = field(default_factory=dict)
27
+
28
+
29
+ def _registry_path(handle: ResearchProjectHandle) -> Path:
30
+ return handle.root / DEFAULT_EVIDENCE_PATH
31
+
32
+
33
+ def _lock_for(path: Path) -> threading.Lock:
34
+ with _REGISTRY_LOCKS_GUARD:
35
+ lock = _REGISTRY_LOCKS.get(path)
36
+ if lock is None:
37
+ lock = threading.Lock()
38
+ _REGISTRY_LOCKS[path] = lock
39
+ return lock
40
+
41
+
42
+ def _read_records(handle: ResearchProjectHandle) -> list[EvidenceRecord]:
43
+ path = _registry_path(handle)
44
+ if not path.exists():
45
+ return []
46
+ payload = json.loads(path.read_text(encoding="utf-8"))
47
+ return [EvidenceRecord(**item) for item in payload]
48
+
49
+
50
+ def _write_records(handle: ResearchProjectHandle, records: list[EvidenceRecord]) -> None:
51
+ path = _registry_path(handle)
52
+ temp_path = path.with_suffix(".tmp")
53
+ temp_path.write_text(
54
+ json.dumps([asdict(record) for record in records], indent=2, ensure_ascii=False),
55
+ encoding="utf-8",
56
+ )
57
+ temp_path.replace(path)
58
+
59
+
60
+ # @id CODE-AISCI-020
61
+ # @implements REQ-AISCI-020
62
+ # @design DES-AISCI-016
63
+ def record_evidence(
64
+ handle: ResearchProjectHandle,
65
+ phase: str,
66
+ artifact_path: Path | str,
67
+ artifact_kind: str,
68
+ timestamp: str,
69
+ metadata: dict[str, str] | None = None,
70
+ ) -> EvidenceRecord:
71
+ """Append one evidence record atomically."""
72
+ path = _registry_path(handle)
73
+ with _lock_for(path):
74
+ records = _read_records(handle)
75
+ record = EvidenceRecord(
76
+ project=handle.name,
77
+ phase=phase,
78
+ artifact_path=str(Path(artifact_path)),
79
+ artifact_kind=artifact_kind,
80
+ created_at=timestamp,
81
+ metadata=metadata or {},
82
+ )
83
+ records.append(record)
84
+ _write_records(handle, records)
85
+ return record
86
+
87
+
88
+ def query_evidence(handle: ResearchProjectHandle, phase: str | None = None) -> list[EvidenceRecord]:
89
+ """Return all evidence, optionally filtered to one phase."""
90
+ records = _read_records(handle)
91
+ if phase is None:
92
+ return records
93
+ return [record for record in records if record.phase == phase]
94
+
95
+
96
+ def latest_evidence(handle: ResearchProjectHandle, phase: str) -> EvidenceRecord | None:
97
+ """Return the most recently recorded evidence for ``phase``."""
98
+ records = query_evidence(handle, phase)
99
+ return records[-1] if records else None
@@ -0,0 +1,20 @@
1
+ """Experimental design phase handler."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+
7
+ from ai_scientist.evidence_registry import record_evidence
8
+ from ai_scientist.project_handle import ResearchProjectHandle
9
+
10
+
11
+ def _now() -> str:
12
+ return datetime.now(timezone.utc).isoformat()
13
+
14
+
15
+ def handle_experimental_design(handle: ResearchProjectHandle, instruction: str) -> dict:
16
+ """Create an experimental design artifact and record it."""
17
+ artifact = handle.design_dir / "experimental-design.md"
18
+ artifact.write_text(instruction, encoding="utf-8")
19
+ record_evidence(handle, "experimental-design", artifact, "markdown", _now())
20
+ return {"phase": "experimental-design", "artifact_path": str(artifact)}
@@ -0,0 +1,14 @@
1
+ """Language detection for ai_scientist responses and metadata."""
2
+
3
+ from ai_data_scientist.language_router import detect_language as _detect_language
4
+
5
+
6
+ # @id CODE-AISCI-001
7
+ # @implements REQ-AISCI-001
8
+ # @design DES-AISCI-001
9
+ # @id CODE-AISCI-010
10
+ # @implements REQ-AISCI-010
11
+ # @design DES-AISCI-007
12
+ def detect_language(text: str) -> str:
13
+ """Reuse ai-data-scientist's deterministic language detector."""
14
+ return _detect_language(text)
@@ -0,0 +1,41 @@
1
+ """LaTeX rendering for tech-writer Markdown output."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+
8
+ def _escape_latex(text: str) -> str:
9
+ replacements = {
10
+ "\\": r"\textbackslash{}",
11
+ "&": r"\&",
12
+ "%": r"\%",
13
+ "$": r"\$",
14
+ "#": r"\#",
15
+ "_": r"\_",
16
+ "{": r"\{",
17
+ "}": r"\}",
18
+ "~": r"\textasciitilde{}",
19
+ "^": r"\textasciicircum{}",
20
+ }
21
+ return "".join(replacements.get(character, character) for character in text)
22
+
23
+
24
+ # @id CODE-AISCI-022
25
+ # @implements REQ-AISCI-022
26
+ # @design DES-AISCI-018
27
+ def render_latex(markdown_path: Path) -> str:
28
+ """Render a small LaTeX document while preserving section order/content."""
29
+ body_lines: list[str] = []
30
+ for raw_line in markdown_path.read_text(encoding="utf-8").splitlines():
31
+ line = raw_line.strip()
32
+ if not line:
33
+ body_lines.append("")
34
+ elif line.startswith("# "):
35
+ body_lines.append(f"\\section{{{_escape_latex(line[2:])}}}")
36
+ elif line.startswith("## "):
37
+ body_lines.append(f"\\subsection{{{_escape_latex(line[3:])}}}")
38
+ else:
39
+ body_lines.append(_escape_latex(line))
40
+ content = "\n".join(body_lines).strip()
41
+ return f"\\documentclass{{article}}\n\\begin{{document}}\n{content}\n\\end{{document}}\n"
@@ -0,0 +1,37 @@
1
+ """Literature review phase handler."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+
7
+ from ai_scientist.evidence_registry import record_evidence
8
+ from ai_scientist.mcp_gateway import McpGateway
9
+ from ai_scientist.project_handle import ResearchProjectHandle
10
+
11
+
12
+ def _now() -> str:
13
+ return datetime.now(timezone.utc).isoformat()
14
+
15
+
16
+ def handle_literature_review(
17
+ handle: ResearchProjectHandle,
18
+ instruction: str,
19
+ gateway: McpGateway | None = None,
20
+ server_name: str | None = None,
21
+ tool_name: str = "literature-search",
22
+ tool_args: dict | None = None,
23
+ ) -> dict:
24
+ """Write a literature artifact, routing external lookups only through MCP."""
25
+ artifact = handle.literature_dir / "literature-review.md"
26
+ content = instruction
27
+ if gateway is not None and server_name is not None:
28
+ tool_result = gateway.call_tool(
29
+ server_name,
30
+ tool_name,
31
+ tool_args or {"query": instruction},
32
+ phase="literature-review",
33
+ )
34
+ content = str(tool_result.get("content", ""))
35
+ artifact.write_text(content, encoding="utf-8")
36
+ record_evidence(handle, "literature-review", artifact, "markdown", _now())
37
+ return {"phase": "literature-review", "artifact_path": str(artifact)}
@@ -0,0 +1,87 @@
1
+ """Phase manifest loading and repo-local skill registry verification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import json
7
+ import re
8
+ from pathlib import Path
9
+
10
+ REPO_ROOT = Path(__file__).resolve().parents[2]
11
+ DEFAULT_SKILL_DIR = REPO_ROOT / ".github" / "skills" / "ai-scientist"
12
+ DEFAULT_MANIFEST_PATH = DEFAULT_SKILL_DIR / "manifest.json"
13
+ VENDORED_PATH = REPO_ROOT / ".github" / "skills" / "VENDORED.md"
14
+
15
+
16
+ class ManifestVerificationError(ValueError):
17
+ """Manifest verification failed."""
18
+
19
+
20
+ def _import_handler(module_path: str, function_name: str) -> None:
21
+ module = importlib.import_module(module_path)
22
+ handler = getattr(module, function_name)
23
+ if not callable(handler):
24
+ raise ManifestVerificationError(f"{module_path}.{function_name} is not callable")
25
+
26
+
27
+ def _validate_skill_dependencies(phase: str, payload):
28
+ if phase == "peer-review":
29
+ if not isinstance(payload, dict) or set(payload) != {"ja", "en"}:
30
+ raise ManifestVerificationError("peer-review skillDependencies must contain ja and en")
31
+ return
32
+ if not isinstance(payload, list):
33
+ raise ManifestVerificationError(f"{phase}: skillDependencies must be a list")
34
+
35
+
36
+ def _load_vendored_versions() -> dict[str, str]:
37
+ text = VENDORED_PATH.read_text(encoding="utf-8")
38
+ versions: dict[str, str] = {}
39
+ for skill_id, version in re.findall(r"\| `([^`]+)` \| [^|]+ \| `([^`]+)` \|", text):
40
+ versions[skill_id] = version.removeprefix("v")
41
+ return versions
42
+
43
+
44
+ def scan_repo_skill_registry() -> dict[str, str | None]:
45
+ """Scan repo-local vendored skills as the verifiable startup registry."""
46
+ versions = _load_vendored_versions()
47
+ registry: dict[str, str | None] = {}
48
+ skills_dir = REPO_ROOT / ".github" / "skills"
49
+ for skill_path in skills_dir.glob("*/SKILL.md"):
50
+ lines = skill_path.read_text(encoding="utf-8").splitlines()
51
+ name_line = next((line for line in lines if line.startswith("name: ")), None)
52
+ if name_line is not None:
53
+ skill_id = name_line.split(":", 1)[1].strip()
54
+ registry[skill_id] = versions.get(skill_id)
55
+ return registry
56
+
57
+
58
+ # @id CODE-AISCI-023
59
+ # @implements REQ-AISCI-023
60
+ # @design DES-AISCI-015
61
+ def load_phase_manifest(path: Path | str = DEFAULT_MANIFEST_PATH) -> dict:
62
+ """Load the machine-readable ai_scientist phase manifest."""
63
+ manifest = json.loads(Path(path).read_text(encoding="utf-8"))
64
+ for phase, entry in manifest.items():
65
+ module_path = entry.get("modulePath")
66
+ function_name = entry.get("functionName")
67
+ if not module_path or not function_name:
68
+ raise ManifestVerificationError(f"{phase}: missing modulePath or functionName")
69
+ _validate_skill_dependencies(phase, entry.get("skillDependencies", []))
70
+ _import_handler(module_path, function_name)
71
+ return manifest
72
+
73
+
74
+ def verify_manifest_against_registry(manifest: dict, registry: dict[str, str | None]) -> None:
75
+ """Verify every declared dependency exists in the startup registry scan."""
76
+ for phase, entry in manifest.items():
77
+ dependencies = entry.get("skillDependencies", [])
78
+ phase_dependencies = (
79
+ dependencies.values() if isinstance(dependencies, dict) else dependencies
80
+ )
81
+ for dependency in phase_dependencies:
82
+ skill_id = dependency["skillId"]
83
+ version = dependency["version"]
84
+ if registry.get(skill_id) != version:
85
+ raise ManifestVerificationError(
86
+ f"{phase}: missing registry entry for {skill_id}@{version}"
87
+ )