starforge-kernel 0.1.0__tar.gz → 0.1.3__tar.gz

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 (35) hide show
  1. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/PKG-INFO +1 -1
  2. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/pyproject.toml +31 -31
  3. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/__init__.py +86 -86
  4. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/core/spec.py +6 -1
  5. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/mcp.py +21 -12
  6. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge_kernel.egg-info/PKG-INFO +1 -1
  7. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/tests/test_mcp_module.py +3 -0
  8. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/README.md +0 -0
  9. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/setup.cfg +0 -0
  10. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/core/__init__.py +0 -0
  11. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/core/checkpoints.py +0 -0
  12. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/core/figures.py +0 -0
  13. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/core/previews.py +0 -0
  14. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/core/provenance.py +0 -0
  15. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/core/runner.py +0 -0
  16. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/core/serializers.py +0 -0
  17. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/index/__init__.py +0 -0
  18. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/index/scanner.py +0 -0
  19. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/kernel/__init__.py +0 -0
  20. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/kernel/__main__.py +0 -0
  21. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/kernel/server.py +0 -0
  22. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge/kernel/worker.py +0 -0
  23. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge_kernel.egg-info/SOURCES.txt +0 -0
  24. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge_kernel.egg-info/dependency_links.txt +0 -0
  25. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge_kernel.egg-info/requires.txt +0 -0
  26. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/src/starforge_kernel.egg-info/top_level.txt +0 -0
  27. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/tests/test_decorator.py +0 -0
  28. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/tests/test_figures.py +0 -0
  29. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/tests/test_indexer.py +0 -0
  30. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/tests/test_kernel_protocol.py +0 -0
  31. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/tests/test_m1_features.py +0 -0
  32. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/tests/test_previews.py +0 -0
  33. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/tests/test_provenance.py +0 -0
  34. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/tests/test_runner_end_to_end.py +0 -0
  35. {starforge_kernel-0.1.0 → starforge_kernel-0.1.3}/tests/test_serializers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: starforge-kernel
3
- Version: 0.1.0
3
+ Version: 0.1.3
4
4
  Summary: *Forge — pipeline canvas, checkpointing, and stale/hydrate execution for the repo you already have open
5
5
  Author: Jonathan Potter
6
6
  License-Expression: Apache-2.0
@@ -1,31 +1,31 @@
1
- [build-system]
2
- requires = ["setuptools>=68", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- # PyPI name `starforge` is squatted by a dormant Galaxy tool (see DESIGN.md §4);
7
- # the import name is still `starforge`.
8
- name = "starforge-kernel"
9
- version = "0.1.0"
10
- description = "*Forge — pipeline canvas, checkpointing, and stale/hydrate execution for the repo you already have open"
11
- readme = "README.md"
12
- authors = [{ name = "Jonathan Potter" }]
13
- license = "Apache-2.0"
14
- requires-python = ">=3.10"
15
- # Intentionally empty: the decorator must import in microseconds inside user
16
- # production code, and the kernel runs stdlib-only. pandas/numpy/pyarrow are
17
- # probed lazily in workers and used only if the workspace env provides them.
18
- dependencies = []
19
-
20
- [project.urls]
21
- Homepage = "https://github.com/Jonpot/forge"
22
-
23
- [project.optional-dependencies]
24
- dev = ["pytest>=8.0", "pandas>=2.0", "pyarrow>=15.0", "numpy>=1.26"]
25
- mcp = ["mcp>=1.8"]
26
-
27
- [tool.setuptools.packages.find]
28
- where = ["src"]
29
-
30
- [tool.pytest.ini_options]
31
- testpaths = ["tests"]
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ # PyPI name `starforge` is squatted by a dormant Galaxy tool (see DESIGN.md §4);
7
+ # the import name is still `starforge`.
8
+ name = "starforge-kernel"
9
+ version = "0.1.3"
10
+ description = "*Forge — pipeline canvas, checkpointing, and stale/hydrate execution for the repo you already have open"
11
+ readme = "README.md"
12
+ authors = [{ name = "Jonathan Potter" }]
13
+ license = "Apache-2.0"
14
+ requires-python = ">=3.10"
15
+ # Intentionally empty: the decorator must import in microseconds inside user
16
+ # production code, and the kernel runs stdlib-only. pandas/numpy/pyarrow are
17
+ # probed lazily in workers and used only if the workspace env provides them.
18
+ dependencies = []
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/Jonpot/forge"
22
+
23
+ [project.optional-dependencies]
24
+ dev = ["pytest>=8.0", "pandas>=2.0", "pyarrow>=15.0", "numpy>=1.26"]
25
+ mcp = ["mcp>=1.8"]
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
29
+
30
+ [tool.pytest.ini_options]
31
+ testpaths = ["tests"]
@@ -1,86 +1,86 @@
1
- """*Forge — pipeline canvas for the repo you already have open.
2
-
3
- This top-level module is the entire public surface that user code touches.
4
- It must import in microseconds and depend on nothing: the decorator lives in
5
- production codebases and has to be free. Everything heavy (indexer, engine,
6
- kernel) lives in submodules that only *Forge itself* imports.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- __version__ = "0.1.0"
12
-
13
- __all__ = ["block", "progress", "BLOCK_ATTR"]
14
-
15
- #: Attribute set on decorated functions. The AST indexer matches the decorator
16
- #: syntactically and never imports user code; this runtime tag exists so user
17
- #: code and future runtime introspection can also recognize blocks.
18
- BLOCK_ATTR = "__starforge_block__"
19
-
20
-
21
- def block(fn=None, *, label=None, category=None, outputs=None):
22
- """Register a function as a *Forge block.
23
-
24
- Usable bare or with keyword arguments::
25
-
26
- @block
27
- def clean(raw: pd.DataFrame) -> pd.DataFrame: ...
28
-
29
- @block(label="Clean AUC Matrix", category="QC", outputs=("clean", "stats"))
30
- def clean_auc(raw, min_coverage: float = 0.8): ...
31
-
32
- The decorated function is returned unchanged — behavior under pytest, in
33
- CI, or in production is identical whether or not *Forge is anywhere near.
34
-
35
- Args:
36
- label: Palette display name. Defaults to the function name, title-cased.
37
- category: Palette grouping. Defaults to the defining module's path.
38
- outputs: Names for multiple return values (function must return a tuple
39
- of the same length). Defaults to a single output named "output".
40
-
41
- Note for palette metadata: the indexer reads ``label``/``category``/
42
- ``outputs`` from the *source*, so they must be literals at the decoration
43
- site to appear in the palette.
44
- """
45
-
46
- def apply(f):
47
- setattr(
48
- f,
49
- BLOCK_ATTR,
50
- {
51
- "label": label,
52
- "category": category,
53
- "outputs": tuple(outputs) if outputs is not None else None,
54
- },
55
- )
56
- return f
57
-
58
- if fn is not None:
59
- return apply(fn)
60
- return apply
61
-
62
-
63
- #: Installed by the *Forge run worker around each block call; None everywhere
64
- #: else, which keeps progress() a guaranteed no-op in pytest/CI/production.
65
- _progress_hook = None
66
-
67
-
68
- def progress(current=None, total=None, label=None):
69
- """Report block progress to the *Forge canvas.
70
-
71
- Call freely inside a block::
72
-
73
- for i, chunk in enumerate(chunks):
74
- progress(i + 1, len(chunks), "fitting folds")
75
- ...
76
-
77
- Outside a *Forge run this does nothing and costs one attribute read —
78
- safe to leave in production code. Any combination of arguments works:
79
- (current, total) renders a determinate bar, label alone updates the text.
80
- """
81
- hook = _progress_hook
82
- if hook is not None:
83
- try:
84
- hook(current, total, label)
85
- except Exception:
86
- pass # progress must never break user code
1
+ """*Forge — pipeline canvas for the repo you already have open.
2
+
3
+ This top-level module is the entire public surface that user code touches.
4
+ It must import in microseconds and depend on nothing: the decorator lives in
5
+ production codebases and has to be free. Everything heavy (indexer, engine,
6
+ kernel) lives in submodules that only *Forge itself* imports.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ __version__ = "0.1.3"
12
+
13
+ __all__ = ["block", "progress", "BLOCK_ATTR"]
14
+
15
+ #: Attribute set on decorated functions. The AST indexer matches the decorator
16
+ #: syntactically and never imports user code; this runtime tag exists so user
17
+ #: code and future runtime introspection can also recognize blocks.
18
+ BLOCK_ATTR = "__starforge_block__"
19
+
20
+
21
+ def block(fn=None, *, label=None, category=None, outputs=None):
22
+ """Register a function as a *Forge block.
23
+
24
+ Usable bare or with keyword arguments::
25
+
26
+ @block
27
+ def clean(raw: pd.DataFrame) -> pd.DataFrame: ...
28
+
29
+ @block(label="Clean AUC Matrix", category="QC", outputs=("clean", "stats"))
30
+ def clean_auc(raw, min_coverage: float = 0.8): ...
31
+
32
+ The decorated function is returned unchanged — behavior under pytest, in
33
+ CI, or in production is identical whether or not *Forge is anywhere near.
34
+
35
+ Args:
36
+ label: Palette display name. Defaults to the function name, title-cased.
37
+ category: Palette grouping. Defaults to the defining module's path.
38
+ outputs: Names for multiple return values (function must return a tuple
39
+ of the same length). Defaults to a single output named "output".
40
+
41
+ Note for palette metadata: the indexer reads ``label``/``category``/
42
+ ``outputs`` from the *source*, so they must be literals at the decoration
43
+ site to appear in the palette.
44
+ """
45
+
46
+ def apply(f):
47
+ setattr(
48
+ f,
49
+ BLOCK_ATTR,
50
+ {
51
+ "label": label,
52
+ "category": category,
53
+ "outputs": tuple(outputs) if outputs is not None else None,
54
+ },
55
+ )
56
+ return f
57
+
58
+ if fn is not None:
59
+ return apply(fn)
60
+ return apply
61
+
62
+
63
+ #: Installed by the *Forge run worker around each block call; None everywhere
64
+ #: else, which keeps progress() a guaranteed no-op in pytest/CI/production.
65
+ _progress_hook = None
66
+
67
+
68
+ def progress(current=None, total=None, label=None):
69
+ """Report block progress to the *Forge canvas.
70
+
71
+ Call freely inside a block::
72
+
73
+ for i, chunk in enumerate(chunks):
74
+ progress(i + 1, len(chunks), "fitting folds")
75
+ ...
76
+
77
+ Outside a *Forge run this does nothing and costs one attribute read —
78
+ safe to leave in production code. Any combination of arguments works:
79
+ (current, total) renders a determinate bar, label alone updates the text.
80
+ """
81
+ hook = _progress_hook
82
+ if hook is not None:
83
+ try:
84
+ hook(current, total, label)
85
+ except Exception:
86
+ pass # progress must never break user code
@@ -123,4 +123,9 @@ class PipelineDoc:
123
123
  return cls.from_json(Path(path).read_text(encoding="utf-8"))
124
124
 
125
125
  def save(self, path: str | Path) -> None:
126
- Path(path).write_text(self.to_json() + "\n", encoding="utf-8")
126
+ # Atomic: the canvas (or any watcher) must never observe a truncated
127
+ # or empty file mid-write — agents and humans edit concurrently.
128
+ target = Path(path)
129
+ tmp = target.with_name(target.name + ".tmp")
130
+ tmp.write_text(self.to_json() + "\n", encoding="utf-8")
131
+ tmp.replace(target)
@@ -207,14 +207,11 @@ def main() -> None:
207
207
  [
208
208
  "*Forge MCP server",
209
209
  f" workspace : {Path(default_workspace).resolve()}",
210
- " transport : stdio waiting for an MCP client to connect on stdin.",
211
- " (Silence is normal: agents launch this server themselves;",
212
- " you rarely need to run it manually.)",
210
+ " transport : stdio (waiting for an MCP client on stdin)",
213
211
  f" tools : {', '.join(TOOL_NAMES)}",
214
212
  "",
215
- "To register with agents, run \"*Forge: Set Up MCP for Agents\" in VS Code,",
216
- "or add to your agent's MCP config:",
217
- ' { "command": "python", "args": ["-m", "starforge.mcp"] } (cwd = your repo)',
213
+ "Register with an agent via \"*Forge: Set Up MCP for Agents\" in VS Code, or:",
214
+ ' { "command": "python", "args": ["-m", "starforge.mcp"] } (cwd = repo root)',
218
215
  "",
219
216
  "Ctrl+C to exit.",
220
217
  ]
@@ -226,12 +223,24 @@ def main() -> None:
226
223
  "starforge",
227
224
  instructions=(
228
225
  "Author and run *Forge pipelines in this workspace. Blocks are @block-decorated "
229
- "functions in the repo (see list_blocks for ids/params/outputs). Pipelines are "
230
- ".forge JSON documents (read one for the schema). write_pipeline validates and "
231
- "returns per-node staleness; run_pipeline executes only stale nodes and returns "
232
- "results; inspect_node shows output previews from checkpoints. Edges target "
233
- "parameter NAMES; params left unwired use literals from the doc or signature "
234
- f"defaults. Default workspace: {Path(default_workspace).resolve()}"
226
+ "functions in the repo (see list_blocks for ids/params/outputs); the decorator is "
227
+ "behavior-neutral, so blocks remain plain callables you can invoke directly when "
228
+ "testing logic outside a pipeline. New @block functions are picked up automatically "
229
+ "on save no registration step. Pipelines are .forge JSON documents shaped like: "
230
+ '{"schema": "starforge/1", "name": "demo", '
231
+ '"nodes": [{"id": "n1", "block": "module:function", "params": {"x": 1}, '
232
+ '"position": {"x": 80, "y": 80}}], '
233
+ '"edges": [{"id": "e1", "source": "n1", "source_output": "output", '
234
+ '"target": "n2", "target_param": "data"}]}. '
235
+ "Canvas annotation boxes live in a top-level \"comments\" array, each shaped "
236
+ '{"id": "c1", "title": "...", "description": "...", "position": {"x": 0, "y": 0}, '
237
+ '"width": 280, "height": 150, "color": "#6366f1"} — these exact field names; '
238
+ "comments never affect execution. "
239
+ "Edges target parameter NAMES; unwired params use doc literals or signature "
240
+ "defaults; `T | None` params with no default receive None. write_pipeline "
241
+ "validates and returns per-node staleness; run_pipeline executes only stale nodes "
242
+ "(pass `target` to run one node's ancestor cone); inspect_node returns checkpoint "
243
+ f"previews. Default workspace: {Path(default_workspace).resolve()}"
235
244
  ),
236
245
  )
237
246
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: starforge-kernel
3
- Version: 0.1.0
3
+ Version: 0.1.3
4
4
  Summary: *Forge — pipeline canvas, checkpointing, and stale/hydrate execution for the repo you already have open
5
5
  Author: Jonathan Potter
6
6
  License-Expression: Apache-2.0
@@ -23,6 +23,9 @@ def test_write_read_list_state_roundtrip(workspace):
23
23
  assert mcp.list_pipelines(ws) == [".forge/pipelines/demo.forge"]
24
24
  doc = mcp.read_pipeline(ws, ".forge/pipelines/demo.forge")
25
25
  assert [n["id"] for n in doc["nodes"]] == ["n1", "n2", "n3"]
26
+ # Saves are atomic — no temp residue for watchers to trip over.
27
+ pipelines_dir = workspace.root / ".forge" / "pipelines"
28
+ assert [p.name for p in pipelines_dir.glob("*.tmp")] == []
26
29
 
27
30
 
28
31
  def test_write_pipeline_rejects_bad_docs_and_escapes(workspace):