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,412 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.9"
4
+ # dependencies = []
5
+ # ///
6
+ """tech-writer skill: a lint script that mechanically checks a technical
7
+ document's *structure* and Markdown rendering safety.
8
+
9
+ Where japanese-prose's GiNZA lint detects sentence-level naturalness
10
+ (vocabulary, rhythm), this script detects structural problems specific to
11
+ technical documents plus Markdown syntax patterns that render inconsistently
12
+ (heading hierarchy, code examples, leftover placeholders, suspicious links,
13
+ and bold delimiters touching prose). The two scripts intentionally don't
14
+ overlap in scope.
15
+
16
+ Findings are flags, not mandates: exit code is always 0 regardless of the
17
+ finding count (it's a lint, so it shouldn't block CI). Exit code 1 is
18
+ reserved for the input file being missing or unreadable.
19
+
20
+ Pass --atomic when linting a commit message, PR description, issue report,
21
+ code comment/docstring, or a single release-notes entry: these atomic
22
+ artifacts follow their own doctype skeleton (see style-constitution.md's
23
+ scope note) and legitimately have no H1 title, so --atomic skips the
24
+ living-document intro-paragraph check that would otherwise misfire on
25
+ them (e.g. assets/templates/pr-description.md lints clean with --atomic).
26
+
27
+ Usage:
28
+ uv run scripts/lint.py <file>
29
+ uv run scripts/lint.py --json <file>
30
+ uv run scripts/lint.py --atomic <file>
31
+ """
32
+ from __future__ import annotations
33
+
34
+ import argparse
35
+ import json
36
+ import re
37
+ import sys
38
+ from dataclasses import dataclass, field
39
+ from pathlib import Path
40
+
41
+
42
+ @dataclass
43
+ class Finding:
44
+ line: int
45
+ category: str
46
+ message: str
47
+ snippet: str = ""
48
+
49
+
50
+ @dataclass
51
+ class LintResult:
52
+ file: str
53
+ findings: list = field(default_factory=list)
54
+
55
+ def to_dict(self) -> dict:
56
+ return {
57
+ "file": self.file,
58
+ "finding_count": len(self.findings),
59
+ "findings": [f.__dict__ for f in self.findings],
60
+ }
61
+
62
+
63
+ HEADING_RE = re.compile(r"^(#{1,6})\s+(.*)$")
64
+ # Generic heading labels that read as content-free in either language.
65
+ GENERIC_HEADINGS = {
66
+ "overview", "introduction", "usage", "notes", "misc", "others",
67
+ "概要", "はじめに", "使い方", "使用方法", "注意点", "注意事項", "その他", "補足",
68
+ }
69
+ # A fence marker is 3+ backticks or 3+ tildes, optionally followed by an
70
+ # info string (e.g. the language). CommonMark requires the closing fence to
71
+ # use the same character and be at least as long as the opener, with no
72
+ # info string of its own.
73
+ FENCE_RE = re.compile(r"^(`{3,}|~{3,})(.*)$")
74
+ PLACEHOLDER_RE = re.compile(r"\b(TODO|FIXME|TBD|XXX)\b", re.IGNORECASE)
75
+ MD_LINK_RE = re.compile(r"\[([^\]]*)\]\(([^)]+)\)")
76
+ INLINE_CODE_RE = re.compile(r"`[^`\n]+`")
77
+ BOLD_RE = re.compile(r"(?<![\\*])\*\*(?!\s)(.+?)(?<!\s)\*\*(?!\*)")
78
+ NON_PARAGRAPH_RE = re.compile(
79
+ r"^(?:[-*+]\s|\d+[.)]\s|>|<!--|\|)|^(?:-{3,}|\*{3,}|_{3,})$"
80
+ )
81
+ # CommonMark indented code block: 4+ leading spaces or a leading tab.
82
+ INDENTED_CODE_RE = re.compile(r"^(?: {4,}|\t)\S")
83
+ # A YAML frontmatter field named "title" with a non-empty value, e.g. a
84
+ # Zenn/Qiita article's `title: "..."` (the platform renders this as the
85
+ # page title, so the body conventionally has no in-body '#' heading).
86
+ TITLE_FIELD_RE = re.compile(r'^title:\s*(["\']?)\S')
87
+
88
+
89
+ def strip_inline_code(line: str) -> str:
90
+ """Blank out inline `code span` contents so placeholder/link checks
91
+ don't fire on tokens that are only being *mentioned* as code, not left
92
+ unresolved in prose (e.g. a doctype guide showing `TODO(#123): ...` as
93
+ an example of the correct form)."""
94
+ return INLINE_CODE_RE.sub(lambda m: " " * len(m.group(0)), line)
95
+
96
+
97
+ def parse_fences(lines: list) -> tuple:
98
+ """Scan for fenced code blocks.
99
+
100
+ Returns (findings, fence_mask) where fence_mask[i] is True when line i
101
+ (0-based) is part of a fenced code block (opening/closing marker or
102
+ content in between). Other checks should skip masked lines so that
103
+ headings, TODOs, or links written *inside* example code blocks aren't
104
+ mistaken for real document structure.
105
+ """
106
+ findings = []
107
+ fence_mask = [False] * len(lines)
108
+ open_char = None
109
+ open_len = 0
110
+ open_line = None
111
+
112
+ def fence_match(raw_line):
113
+ # CommonMark/GFM only recognizes a fence indented by at most three
114
+ # spaces; four or more spaces (or a leading tab, which expands to
115
+ # 4+ columns) is indented code, not a fence.
116
+ expanded = raw_line.expandtabs(4)
117
+ indent = len(expanded) - len(expanded.lstrip(" "))
118
+ if indent > 3:
119
+ return None
120
+ return FENCE_RE.match(raw_line.strip())
121
+
122
+ for i, line in enumerate(lines):
123
+ m = fence_match(line)
124
+ if open_char is None:
125
+ if m:
126
+ marker, info = m.group(1), m.group(2).strip()
127
+ open_char, open_len, open_line = marker[0], len(marker), i + 1
128
+ fence_mask[i] = True
129
+ if not info:
130
+ findings.append(
131
+ Finding(
132
+ line=i + 1,
133
+ category="code_fence_no_lang",
134
+ message="Code block has no language tag (recommended for syntax highlighting and copy detection).",
135
+ snippet=line.strip(),
136
+ )
137
+ )
138
+ continue
139
+
140
+ fence_mask[i] = True
141
+ if m:
142
+ marker, info = m.group(1), m.group(2).strip()
143
+ is_closing = marker[0] == open_char and len(marker) >= open_len and not info
144
+ if is_closing:
145
+ open_char, open_len, open_line = None, 0, None
146
+
147
+ if open_char is not None:
148
+ findings.append(
149
+ Finding(
150
+ line=open_line or 0,
151
+ category="unclosed_code_fence",
152
+ message="A code block may not be closed.",
153
+ )
154
+ )
155
+ return findings, fence_mask
156
+
157
+
158
+ def check_heading_hierarchy(lines: list, fence_mask: list) -> list:
159
+ findings = []
160
+ prev_level = 0
161
+ for i, line in enumerate(lines):
162
+ if fence_mask[i]:
163
+ continue
164
+ m = HEADING_RE.match(line)
165
+ if not m:
166
+ continue
167
+ level = len(m.group(1))
168
+ text = m.group(2).strip()
169
+ if prev_level and level > prev_level + 1:
170
+ findings.append(
171
+ Finding(
172
+ line=i + 1,
173
+ category="heading_skip",
174
+ message=f"Heading level jumps from H{prev_level} to H{level}.",
175
+ snippet=line.strip(),
176
+ )
177
+ )
178
+ stripped = text.rstrip("::").strip().lower()
179
+ if stripped in GENERIC_HEADINGS:
180
+ findings.append(
181
+ Finding(
182
+ line=i + 1,
183
+ category="generic_heading",
184
+ message="Generic heading label; make it preview the content instead (structure constitution rule 2).",
185
+ snippet=line.strip(),
186
+ )
187
+ )
188
+ prev_level = level
189
+ return findings
190
+
191
+
192
+ def check_placeholders(lines: list, fence_mask: list) -> list:
193
+ findings = []
194
+ for i, line in enumerate(lines):
195
+ if fence_mask[i]:
196
+ continue
197
+ checked = strip_inline_code(line)
198
+ for m in PLACEHOLDER_RE.finditer(checked):
199
+ # A justified/tracked marker like "TODO(#123): ..." documents a
200
+ # reason and a tracking reference, which is exactly what this
201
+ # skill's own guidance asks for — don't flag that form. An empty
202
+ # "TODO()" carries no reason at all, so it still gets flagged.
203
+ if checked[m.end():m.end() + 1] == "(":
204
+ close_idx = checked.find(")", m.end())
205
+ content = checked[m.end() + 1:close_idx] if close_idx != -1 else ""
206
+ if content.strip():
207
+ continue
208
+ findings.append(
209
+ Finding(
210
+ line=i + 1,
211
+ category="placeholder",
212
+ message=f"Unresolved placeholder '{m.group(1)}' remains without a reason/tracking reference; resolve before publishing.",
213
+ snippet=line.strip(),
214
+ )
215
+ )
216
+ return findings
217
+
218
+
219
+ def check_links(lines: list, fence_mask: list) -> list:
220
+ findings = []
221
+ for i, line in enumerate(lines):
222
+ if fence_mask[i]:
223
+ continue
224
+ checked = strip_inline_code(line)
225
+ for m in MD_LINK_RE.finditer(checked):
226
+ text, target = m.group(1), m.group(2)
227
+ if not text.strip():
228
+ findings.append(
229
+ Finding(
230
+ line=i + 1,
231
+ category="empty_link_text",
232
+ message="Link text is empty. Avoid content-free link text like 'here'/'こちら' too.",
233
+ snippet=line.strip(),
234
+ )
235
+ )
236
+ if target.strip() in ("#", "", "javascript:void(0)"):
237
+ findings.append(
238
+ Finding(
239
+ line=i + 1,
240
+ category="dead_link_placeholder",
241
+ message="Link target is still an unset placeholder.",
242
+ snippet=line.strip(),
243
+ )
244
+ )
245
+ return findings
246
+
247
+
248
+ def check_bold_spacing(lines: list, fence_mask: list) -> list:
249
+ """Flag bold delimiters with missing or non-ASCII surrounding spaces.
250
+
251
+ Some Markdown renderers fail to recognize strong emphasis when `**`
252
+ directly adjoins Japanese or other word characters. Punctuation and
253
+ line boundaries do not need padding. When padding is present, it must
254
+ be an ASCII half-width space rather than a tab or Unicode space.
255
+ """
256
+ findings = []
257
+ for i, line in enumerate(lines):
258
+ if fence_mask[i]:
259
+ continue
260
+ checked = strip_inline_code(line)
261
+ for match in BOLD_RE.finditer(checked):
262
+ before = checked[match.start() - 1] if match.start() else ""
263
+ after = checked[match.end()] if match.end() < len(checked) else ""
264
+ if (before and (before.isalnum() or before == "_")) or (
265
+ after and (after.isalnum() or after == "_")
266
+ ) or (
267
+ before and before.isspace() and before != " "
268
+ ) or (
269
+ after and after.isspace() and after != " "
270
+ ):
271
+ findings.append(
272
+ Finding(
273
+ line=i + 1,
274
+ category="bold_spacing",
275
+ message=(
276
+ "Use ASCII half-width spaces immediately before "
277
+ "and after '**...**' when it is embedded in prose; "
278
+ "do not use full-width or other Unicode spaces."
279
+ ),
280
+ snippet=line.strip(),
281
+ )
282
+ )
283
+ return findings
284
+
285
+
286
+ def check_intro_paragraph(lines: list, fence_mask: list) -> list:
287
+ """Check that the document opens with a non-empty H1 title immediately
288
+ followed by a genuine body paragraph.
289
+
290
+ This is a heuristic proxy for structure constitution rule 1 ("say what
291
+ this is and the outcome up front"). The very first non-blank line after
292
+ the title must be plain prose — a list item, blockquote, HTML comment,
293
+ table row, thematic break, another heading, a fenced code block, or
294
+ indented code does not count, even if real prose follows it further
295
+ down. A leading YAML frontmatter block (e.g. skill metadata, or a
296
+ platform frontmatter with its own `title:` field such as Zenn/Qiita) is
297
+ skipped before this check begins; fenced code is only skipped while
298
+ still searching for the title itself (a heading can't appear inside
299
+ one).
300
+
301
+ A frontmatter block that already carries a non-empty `title:` field
302
+ counts as satisfying the title requirement on its own — those platforms
303
+ render that field as the page/article title and conventionally don't
304
+ repeat it as an in-body '#' heading.
305
+ """
306
+ start = 0
307
+ frontmatter_has_title = False
308
+ if lines and lines[0].strip() == "---":
309
+ for j in range(1, len(lines)):
310
+ if lines[j].strip() == "---":
311
+ start = j + 1
312
+ break
313
+ if TITLE_FIELD_RE.match(lines[j]):
314
+ frontmatter_has_title = True
315
+ state = "after_title" if frontmatter_has_title else "before_title"
316
+ for i, line in enumerate(lines):
317
+ if i < start:
318
+ continue
319
+ if state == "before_title" and fence_mask[i]:
320
+ continue
321
+ stripped = line.strip()
322
+ if not stripped:
323
+ continue
324
+ if state == "after_title" and (fence_mask[i] or INDENTED_CODE_RE.match(line)):
325
+ # Fenced or indented code right after the title isn't prose.
326
+ break
327
+ m = HEADING_RE.match(stripped)
328
+ if state == "before_title":
329
+ if m and len(m.group(1)) == 1 and m.group(2).strip():
330
+ state = "after_title"
331
+ continue
332
+ # Either the first heading isn't a non-empty top-level title,
333
+ # or non-heading content appeared before any title — either way
334
+ # there's nothing valid to anchor the check against.
335
+ break
336
+ # state == "after_title": the very next non-blank line decides it.
337
+ if m or NON_PARAGRAPH_RE.match(stripped):
338
+ break
339
+ return []
340
+ return [
341
+ Finding(
342
+ line=1,
343
+ category="missing_intro",
344
+ message="Document must open with a non-empty '#' title heading immediately followed by a plain-prose paragraph (not a list, blockquote, comment, table, code block, or another heading) stating what this is and the reader outcome (structure constitution rule 1).",
345
+ )
346
+ ]
347
+
348
+
349
+ def run_lint(path: Path, atomic: bool = False) -> LintResult:
350
+ text = path.read_text(encoding="utf-8")
351
+ lines = text.splitlines()
352
+ result = LintResult(file=str(path))
353
+ fence_findings, fence_mask = parse_fences(lines)
354
+ result.findings.extend(fence_findings)
355
+ result.findings.extend(check_heading_hierarchy(lines, fence_mask))
356
+ result.findings.extend(check_placeholders(lines, fence_mask))
357
+ result.findings.extend(check_links(lines, fence_mask))
358
+ result.findings.extend(check_bold_spacing(lines, fence_mask))
359
+ if not atomic:
360
+ # check_intro_paragraph assumes a living, multi-section document
361
+ # (H1 title + opening paragraph); atomic artifacts like a PR
362
+ # description or a single release-notes entry follow their own
363
+ # doctype skeleton instead (see style-constitution.md's scope
364
+ # note) and legitimately have no H1 at all.
365
+ result.findings.extend(check_intro_paragraph(lines, fence_mask))
366
+ result.findings.sort(key=lambda f: f.line)
367
+ return result
368
+
369
+
370
+ def main() -> int:
371
+ parser = argparse.ArgumentParser(description="tech-writer structural lint")
372
+ parser.add_argument("file", type=str, help="Target Markdown file")
373
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
374
+ parser.add_argument(
375
+ "--atomic",
376
+ action="store_true",
377
+ help=(
378
+ "Lint as an atomic artifact (commit message, PR description, "
379
+ "issue report, code comment/docstring, a single release-notes "
380
+ "entry): skips the living-document intro-paragraph check, "
381
+ "which doesn't apply to these doctypes' own skeletons."
382
+ ),
383
+ )
384
+ args = parser.parse_args()
385
+
386
+ path = Path(args.file)
387
+ if not path.is_file():
388
+ print(f"error: file not found: {path}", file=sys.stderr)
389
+ return 1
390
+
391
+ try:
392
+ result = run_lint(path, atomic=args.atomic)
393
+ except OSError as e:
394
+ print(f"error: failed to read file: {e}", file=sys.stderr)
395
+ return 1
396
+
397
+ if args.json:
398
+ print(json.dumps(result.to_dict(), ensure_ascii=False, indent=2))
399
+ else:
400
+ if not result.findings:
401
+ print(f"{path}: no structural findings.")
402
+ else:
403
+ print(f"{path}: {len(result.findings)} finding(s)")
404
+ for f in result.findings:
405
+ print(f" L{f.line} [{f.category}] {f.message}")
406
+ if f.snippet:
407
+ print(f" > {f.snippet}")
408
+ return 0
409
+
410
+
411
+ if __name__ == "__main__":
412
+ sys.exit(main())
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nahisaho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # Jupytermind:AI Scientist Skill Suite — Jupyter MCP Copilot Agent Skills
2
+
3
+ [日本語 README](README-ja.md)
4
+
5
+ A collection of GitHub Copilot Agent Skills for natural-language (Japanese/
6
+ English) scientific computing over Jupyter via the Jupyter MCP (Datalayer
7
+ `jupyter-mcp-server`). Every reasoning-based insight is recorded in the
8
+ project notebook alongside the executed cell that provides its evidence.
9
+ Built with SDD (`musubix3`) and TDD (pytest).
10
+
11
+ ## Skills included
12
+
13
+ | Skill | Scope |
14
+ | --- | --- |
15
+ | `ai-data-scientist` | Load, clean, explore, analyze, visualize, and draw insights from datasets via Jupyter (MVP + ML extension). |
16
+ | `ai-chemistry-scientist` | Cheminformatics: molecular descriptors, ADMET heuristic screening, QSAR modeling, similarity search, docking-score heuristics, drug-likeness/structural-alert screening, formula/mass calculation, SMILES standardization and format conversion. |
17
+ | `ai-genomics-scientist` | Computational genomics: sequence feature analysis, variant-effect heuristic annotation, splice-site strength scoring, gene-set enrichment analysis, pairwise sequence alignment. |
18
+ | `ai-materials-scientist` | Materials-science simulation: phase-field microstructure evolution, molecular dynamics, classical/kinetic Monte Carlo, crystal plasticity, a simplified FEM solver, and a simplified binary CALPHAD phase diagram. |
19
+ | `ai-structural-biology-scientist` | Structural biology: secondary-structure and hydrophobicity/burial heuristics, protein-protein docking-score heuristics, RMSD-based structural similarity, residue contact-map heuristics. |
20
+ | `ai-scientist` | End-to-end single-project research guidance: planning, literature review, experimental design, data analysis, manuscript writing, peer review, reproducibility checks, and presentation. |
21
+ | `tech-writer` | Structures and polishes technical documents: READMEs, design docs/ADRs, API references, PR descriptions, release notes, user manuals, code comments. |
22
+ | `japanese-prose` | Improves Japanese prose quality using GiNZA-based diagnostics (kotonoha). |
23
+ | `presentation-planner` | Plans PPTX content structure and design handoff (does not generate the file itself). |
24
+ | `sdd-*` (`sdd-change`, `sdd-requirements`, `sdd-design`, `sdd-implementation`, `sdd-quality`, `sdd-traceability`, `sdd-knowledge`, `sdd-formal-codegraph`, `sdd-issue-report`) | The Specification-Driven-Development workflow (`musubix3`) used to build and evolve every skill above. |
25
+
26
+ See each skill's `.github/skills/<name>/SKILL.md` for its full invocation
27
+ instructions and trigger phrases.
28
+
29
+ ## Setup
30
+
31
+ Install the skill from the npm registry (no need to clone this repo):
32
+
33
+ ```sh
34
+ npm install jupytermind
35
+ npx ai-data-scientist doctor
36
+ ```
37
+
38
+ Or, for a local checkout of this repo:
39
+
40
+ ```sh
41
+ npm install
42
+ npx ai-data-scientist doctor
43
+ ```
44
+
45
+ The Python environment (virtualenv + dependencies) is set up automatically
46
+ on first CLI invocation in both cases.
47
+
48
+ `npm install` itself has no dependencies and does not use `postinstall`
49
+ (since npm v12, install scripts of dependencies are disabled by default
50
+ and require `npm approve-scripts`). Instead, `bin/ai-data-scientist.js`
51
+ creates a `.venv` and installs the dependencies from `pyproject.toml` on
52
+ first CLI invocation. Subsequent invocations are cached and start fast.
53
+
54
+ To set up the Python environment manually:
55
+
56
+ ```sh
57
+ python3 -m venv .venv
58
+ .venv/bin/pip install -e .
59
+ .venv/bin/pytest
60
+ ```
61
+
62
+ ### PDF export prerequisites
63
+
64
+ `report_export.export_report(..., report_format="pdf")` uses nbconvert's
65
+ `PDFExporter`, which shells out to a system `xelatex` binary. This is **not**
66
+ installed by `pip`/`npm` and must be provided separately, e.g.:
67
+
68
+ ```sh
69
+ # Debian/Ubuntu
70
+ sudo apt-get install texlive-xetex texlive-fonts-recommended
71
+
72
+ # macOS
73
+ brew install --cask mactex-no-gui
74
+ ```
75
+
76
+ If `xelatex` is not on `PATH`, PDF export raises a `RuntimeError` pointing
77
+ back to this section; use `report_format="html"` if you do not need PDF
78
+ output.
79
+
80
+ ## Skills
81
+
82
+ See `.github/skills/<skill-name>/SKILL.md` for each skill's invocation
83
+ instructions and `.musubix/features/<feature-slug>/requirements.md` for its
84
+ authoritative requirement set.
85
+
86
+ ## License
87
+
88
+ MIT License — see [LICENSE](LICENSE).
89
+
90
+ ## Changelog
91
+
92
+ See [CHANGELOG.md](CHANGELOG.md) for release notes.
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * npm bootstrap entrypoint for the ai-data-scientist Copilot Agent Skill.
6
+ *
7
+ * This script intentionally does NOT run as an npm "postinstall" script:
8
+ * starting with npm v12, install-lifecycle scripts from dependencies are
9
+ * disabled by default and require each consumer to run
10
+ * `npm approve-scripts`, which is not a realistic onboarding step for a
11
+ * published skill package. Instead, environment setup happens lazily on
12
+ * the first invocation of this CLI (`npx ai-data-scientist setup`, or any
13
+ * other subcommand), and is cached via a marker file so later invocations
14
+ * are fast.
15
+ */
16
+
17
+ const { spawnSync } = require("node:child_process");
18
+ const crypto = require("node:crypto");
19
+ const fs = require("node:fs");
20
+ const path = require("node:path");
21
+
22
+ const PACKAGE_ROOT = path.resolve(__dirname, "..");
23
+ const VENV_DIR = path.join(PACKAGE_ROOT, ".venv");
24
+ const IS_WINDOWS = process.platform === "win32";
25
+ const VENV_PYTHON = path.join(
26
+ VENV_DIR,
27
+ IS_WINDOWS ? "Scripts" : "bin",
28
+ IS_WINDOWS ? "python.exe" : "python"
29
+ );
30
+ const PYPROJECT_PATH = path.join(PACKAGE_ROOT, "pyproject.toml");
31
+ const MARKER_PATH = path.join(VENV_DIR, ".ai-data-scientist-setup-complete.json");
32
+
33
+ function log(message) {
34
+ process.stderr.write(`[ai-data-scientist] ${message}\n`);
35
+ }
36
+
37
+ function fileHash(filePath) {
38
+ return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
39
+ }
40
+
41
+ function findPythonLauncher() {
42
+ const candidates = IS_WINDOWS ? ["python", "python3"] : ["python3", "python"];
43
+ for (const candidate of candidates) {
44
+ const probe = spawnSync(candidate, ["--version"], { stdio: "ignore" });
45
+ if (probe.status === 0) return candidate;
46
+ }
47
+ throw new Error(
48
+ "No Python 3 interpreter found on PATH (PATHにPython 3が見つかりません). " +
49
+ "Install Python 3.10+ and re-run this command."
50
+ );
51
+ }
52
+
53
+ function run(command, args, options = {}) {
54
+ const result = spawnSync(command, args, { stdio: "inherit", cwd: PACKAGE_ROOT, ...options });
55
+ if (result.error) throw result.error;
56
+ if (result.status !== 0) {
57
+ throw new Error(`Command failed (${result.status}): ${command} ${args.join(" ")}`);
58
+ }
59
+ }
60
+
61
+ function readMarker() {
62
+ try {
63
+ return JSON.parse(fs.readFileSync(MARKER_PATH, "utf8"));
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ function isSetupCurrent() {
70
+ if (!fs.existsSync(VENV_PYTHON)) return false;
71
+ const marker = readMarker();
72
+ if (!marker) return false;
73
+ return marker.pyprojectSha256 === fileHash(PYPROJECT_PATH);
74
+ }
75
+
76
+ function ensureSetup() {
77
+ if (isSetupCurrent()) return;
78
+
79
+ log("Setting up Python environment (初回セットアップ: Python仮想環境を構築します)...");
80
+ const python = findPythonLauncher();
81
+
82
+ if (!fs.existsSync(VENV_DIR)) {
83
+ run(python, ["-m", "venv", VENV_DIR]);
84
+ }
85
+ run(VENV_PYTHON, ["-m", "pip", "install", "--upgrade", "pip"]);
86
+ run(VENV_PYTHON, ["-m", "pip", "install", "-e", PACKAGE_ROOT]);
87
+
88
+ fs.writeFileSync(
89
+ MARKER_PATH,
90
+ JSON.stringify(
91
+ { pyprojectSha256: fileHash(PYPROJECT_PATH), completedAt: new Date().toISOString() },
92
+ null,
93
+ 2
94
+ )
95
+ );
96
+ log("Setup complete (セットアップが完了しました).");
97
+ }
98
+
99
+ function main() {
100
+ const [command, ...rest] = process.argv.slice(2);
101
+
102
+ ensureSetup();
103
+
104
+ if (!command || command === "setup") {
105
+ log("Environment ready (環境は準備済みです).");
106
+ return;
107
+ }
108
+
109
+ if (command === "test") {
110
+ run(VENV_PYTHON, ["-m", "pytest", ...rest]);
111
+ return;
112
+ }
113
+
114
+ // Pass through to the Python CLI for every other subcommand.
115
+ run(VENV_PYTHON, ["-m", "ai_data_scientist.cli", command, ...rest]);
116
+ }
117
+
118
+ try {
119
+ main();
120
+ } catch (err) {
121
+ log(err.message || String(err));
122
+ process.exit(1);
123
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "jupytermind",
3
+ "version": "0.3.0",
4
+ "description": "GitHub Copilot Agent Skill: AI Data Scientist over Jupyter MCP (npm bootstrap for the Python implementation)",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "ai-data-scientist": "bin/ai-data-scientist.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src/ai_data_scientist/**/*.py",
12
+ "src/ai_chemistry_scientist/**/*.py",
13
+ "src/ai_genomics_scientist/**/*.py",
14
+ "src/ai_materials_scientist/**/*.py",
15
+ "src/ai_structural_biology_scientist/**/*.py",
16
+ "src/ai_scientist/**/*.py",
17
+ "src/ai_chemistry_scientist/data",
18
+ ".github/skills/ai-data-scientist",
19
+ ".github/skills/ai-chemistry-scientist",
20
+ ".github/skills/ai-genomics-scientist",
21
+ ".github/skills/ai-materials-scientist",
22
+ ".github/skills/ai-structural-biology-scientist",
23
+ ".github/skills/ai-scientist",
24
+ ".github/skills/tech-writer",
25
+ ".github/skills/japanese-prose",
26
+ ".github/skills/presentation-planner",
27
+ "pyproject.toml"
28
+ ],
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "scripts": {
33
+ "setup": "node bin/ai-data-scientist.js setup",
34
+ "test": "node bin/ai-data-scientist.js test"
35
+ },
36
+ "os": [
37
+ "linux",
38
+ "darwin",
39
+ "win32"
40
+ ]
41
+ }