oh-my-opencode 5.0.0-beta.26 → 5.0.0-beta.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/config-manager/parse-opencode-config-file.d.ts +2 -1
- package/dist/cli/doctor/checks/system-plugin.d.ts +4 -3
- package/dist/cli/index.js +59 -45
- package/dist/cli-node/index.js +82 -45
- package/dist/hooks/todo-continuation-enforcer/types.d.ts +1 -0
- package/dist/index.js +53 -28
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/legacy-plugin-warning.d.ts +2 -1
- package/dist/shared/plugin-entry-migrator.d.ts +4 -3
- package/dist/shared/plugin-entry-shape.d.ts +6 -0
- package/dist/skills/data-scientist/SKILL.md +99 -239
- package/dist/skills/data-scientist/references/execution-surfaces.md +91 -0
- package/dist/skills/data-scientist/references/placement.md +74 -0
- package/dist/skills/data-scientist/references/polars-lane.md +95 -0
- package/dist/skills/data-scientist/references/uv-setup.md +1 -1
- package/dist/skills/data-scientist/references/visualization.md +64 -0
- package/dist/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
- package/dist/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
- package/dist/skills/ulw-research/SKILL.md +3 -1
- package/dist/tui.d.ts +12 -0
- package/dist/tui.js +3 -3
- package/package.json +15 -15
- package/packages/lsp-core/src/request-context.test.ts +29 -0
- package/packages/lsp-core/src/request-context.ts +1 -1
- package/packages/lsp-daemon/dist/cli.js +1 -1
- package/packages/lsp-daemon/dist/client.js +1 -1
- package/packages/lsp-daemon/dist/index.js +1 -1
- package/packages/lsp-tools-mcp/dist/cli.js +1 -1
- package/packages/lsp-tools-mcp/dist/mcp.js +1 -1
- package/packages/lsp-tools-mcp/dist/request-context.js +1 -1
- package/packages/lsp-tools-mcp/dist/tools.js +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/data-scientist/SKILL.md +99 -239
- package/packages/omo-codex/plugin/skills/data-scientist/references/execution-surfaces.md +91 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/placement.md +74 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/polars-lane.md +95 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/uv-setup.md +1 -1
- package/packages/omo-codex/plugin/skills/data-scientist/references/visualization.md +64 -0
- package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
- package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +3 -1
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
- package/packages/shared-skills/skills/data-scientist/SKILL.md +99 -239
- package/packages/shared-skills/skills/data-scientist/references/execution-surfaces.md +91 -0
- package/packages/shared-skills/skills/data-scientist/references/placement.md +74 -0
- package/packages/shared-skills/skills/data-scientist/references/polars-lane.md +95 -0
- package/packages/shared-skills/skills/data-scientist/references/uv-setup.md +1 -1
- package/packages/shared-skills/skills/data-scientist/references/visualization.md +64 -0
- package/packages/shared-skills/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
- package/packages/shared-skills/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
- package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -1
- package/dist/skills/data-scientist/references/common-scenarios.md +0 -176
- package/dist/skills/data-scientist/references/execution-templates.md +0 -197
- package/dist/skills/data-scientist/references/integration-patterns.md +0 -153
- package/dist/skills/data-scientist/references/performance-benchmarks.md +0 -37
- package/packages/omo-codex/plugin/skills/data-scientist/references/common-scenarios.md +0 -176
- package/packages/omo-codex/plugin/skills/data-scientist/references/execution-templates.md +0 -197
- package/packages/omo-codex/plugin/skills/data-scientist/references/integration-patterns.md +0 -153
- package/packages/omo-codex/plugin/skills/data-scientist/references/performance-benchmarks.md +0 -37
- package/packages/shared-skills/skills/data-scientist/references/common-scenarios.md +0 -176
- package/packages/shared-skills/skills/data-scientist/references/execution-templates.md +0 -197
- package/packages/shared-skills/skills/data-scientist/references/integration-patterns.md +0 -153
- package/packages/shared-skills/skills/data-scientist/references/performance-benchmarks.md +0 -37
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Execution surfaces
|
|
2
|
+
|
|
3
|
+
How to run the engines on each surface, and when to escalate between them.
|
|
4
|
+
|
|
5
|
+
## Persistent kernel, JavaScript (Bun)
|
|
6
|
+
|
|
7
|
+
One-time setup per machine — the bundled script installs `@duckdb/node-api` into a user-level
|
|
8
|
+
cache outside any repo and prints the absolute import path (its only stdout line):
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
bash scripts/ensure-js-deps.sh # run from the skill directory
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
In the kernel — top-level `require` may not exist, dynamic import always works:
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
const { DuckDBInstance } = await import("<printed path>");
|
|
18
|
+
const db = await DuckDBInstance.create(":memory:");
|
|
19
|
+
const conn = await db.connect();
|
|
20
|
+
const reader = await conn.runAndReadAll("SELECT category, SUM(v) AS total FROM 'data.csv' GROUP BY 1");
|
|
21
|
+
reader.getRowObjects(); // array of plain row objects
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- The connection and any tables created live across cells — connect once per session, reuse.
|
|
25
|
+
- COUNT/SUM over integer columns return BigInt; convert (`Number(x)` or `String(x)`) before
|
|
26
|
+
`JSON.stringify`, which throws on BigInt.
|
|
27
|
+
- Bun builtins cover ingest gaps with zero installs: `Bun.JSONL.parse`, `Bun.JSON5.parse`,
|
|
28
|
+
`Bun.XML.parse`, `Bun.TOML.parse`, `Bun.Archive` for tarballs.
|
|
29
|
+
- nodejs-polars is NOT part of this skill's toolkit: its API lags the Python release by
|
|
30
|
+
major versions (option objects that work in Python throw napi type errors). Polars work
|
|
31
|
+
belongs to the Python kernel (below).
|
|
32
|
+
|
|
33
|
+
## Persistent kernel, Python (the default Python surface)
|
|
34
|
+
|
|
35
|
+
duckdb, numpy, and matplotlib are typically resident — import and use them directly.
|
|
36
|
+
Polars and pyarrow rarely ship with a kernel, so inject them once per session (run from the
|
|
37
|
+
skill directory; the script installs on first use, then just prints the path):
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import subprocess, sys
|
|
41
|
+
site = subprocess.run(["bash", "scripts/ensure-py-deps.sh", sys.executable],
|
|
42
|
+
capture_output=True, text=True, check=True).stdout.strip()
|
|
43
|
+
sys.path.insert(0, site)
|
|
44
|
+
import polars as pl
|
|
45
|
+
import pyarrow
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- The install goes to a user cache keyed to the kernel's interpreter version; the
|
|
49
|
+
interpreter itself is never mutated (it is frequently an externally-managed system
|
|
50
|
+
Python, and mutating it breaks other tools).
|
|
51
|
+
- After injection the whole Python stack is resident: `duckdb.sql(...).pl()` hands off via
|
|
52
|
+
Arrow, `duckdb.register(name, df)` goes the other way, and Polars lazy pipelines run
|
|
53
|
+
in-kernel across cells.
|
|
54
|
+
- `duckdb.sql("SELECT ... FROM 'data.csv'")` queries files in place; without the injection,
|
|
55
|
+
keep results in DuckDB or fetch plain Python values (`.fetchall()`).
|
|
56
|
+
- matplotlib figures render natively in kernels that display rich output; also save a PNG so
|
|
57
|
+
the artifact survives the session.
|
|
58
|
+
|
|
59
|
+
## uv lane (fallback and isolation)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv run --with duckdb --with polars --with pyarrow --with numpy python -c "<code>"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- Reach for it when there is no kernel, or when a heavy, crash-prone one-shot should not
|
|
66
|
+
run inside (and possibly take down) the kernel.
|
|
67
|
+
- Include exactly the packages the code imports, plus pyarrow whenever `.pl()` is used.
|
|
68
|
+
- Each invocation pays process spawn plus imports (roughly 0.3s warm) and re-reads its inputs —
|
|
69
|
+
fine for one-shots, wasteful for exploration loops.
|
|
70
|
+
- Past a few lines, a temp file beats `-c` quoting: write the script, `uv run script.py`.
|
|
71
|
+
|
|
72
|
+
## No kernel at all
|
|
73
|
+
|
|
74
|
+
Same engines, one process per batch of questions:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
bun -e '<the JavaScript kernel pattern above>' # DuckDB via @duckdb/node-api
|
|
78
|
+
uv run --with duckdb python -c "<sql via duckdb.sql>" # DuckDB via Python
|
|
79
|
+
uv run scripts/quick-query.py data.csv "SELECT ..." # zero-code CLI fallback
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Escalation rules
|
|
83
|
+
|
|
84
|
+
Start on the resident kernel. Move a step down when a concrete need appears:
|
|
85
|
+
|
|
86
|
+
- polars/pyarrow missing from the kernel — inject via `ensure-py-deps.sh` (above), not a
|
|
87
|
+
uv one-shot.
|
|
88
|
+
- Crash-prone or memory-hungry one-shot that should not take the kernel down — uv lane.
|
|
89
|
+
- No kernel on this harness — one-shot recipes above.
|
|
90
|
+
- Data lives remotely or exceeds local RAM — read `placement.md` and move the query, not
|
|
91
|
+
the data.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Placement: where should this computation live?
|
|
2
|
+
|
|
3
|
+
Decide before touching the data. Wrong placement wastes minutes (re-scanning a file queried
|
|
4
|
+
ten times) or kills the machine (loading a dataset larger than RAM and swapping).
|
|
5
|
+
|
|
6
|
+
## The probe (run first, once)
|
|
7
|
+
|
|
8
|
+
Three facts, one cell or script:
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
import os, shutil, subprocess, sys
|
|
12
|
+
size = os.path.getsize("data.csv") # bytes on disk
|
|
13
|
+
disk_free = shutil.disk_usage(".").free # spill headroom
|
|
14
|
+
if sys.platform == "darwin":
|
|
15
|
+
ram = int(subprocess.run(["sysctl", "-n", "hw.memsize"], capture_output=True, text=True).stdout)
|
|
16
|
+
else:
|
|
17
|
+
ram = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES")
|
|
18
|
+
# row estimate without loading (DuckDB streams the scan):
|
|
19
|
+
# duckdb.sql("SELECT count(*) FROM 'data.csv'")
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
CSV typically expands 2-5x in memory (string columns dominate); Parquet expands less
|
|
23
|
+
predictably — compressed columns can inflate 10x. Estimate the working set from the
|
|
24
|
+
decompressed size of the columns the question actually touches, not the file size.
|
|
25
|
+
|
|
26
|
+
## In memory — load once, iterate
|
|
27
|
+
|
|
28
|
+
When the working set stays within roughly 25% of free RAM AND the session will run repeated
|
|
29
|
+
queries: load once (`CREATE TABLE t AS SELECT ...` in DuckDB, or a collected DataFrame),
|
|
30
|
+
then iterate. One scan up front converts every later query from a file re-scan into
|
|
31
|
+
milliseconds. Prune at load time — select only the needed columns, filter obvious dross —
|
|
32
|
+
so the resident table is the working set, not the raw file.
|
|
33
|
+
|
|
34
|
+
## In place / streaming — single pass, or bigger than RAM
|
|
35
|
+
|
|
36
|
+
- Single-pass questions: query the file directly (`FROM 'data.csv'`). Loading first is pure
|
|
37
|
+
waste.
|
|
38
|
+
- Bigger than RAM, SQL-shaped: cap DuckDB and let it spill —
|
|
39
|
+
|
|
40
|
+
```sql
|
|
41
|
+
SET memory_limit = '4GB';
|
|
42
|
+
SET temp_directory = '/tmp/duckdb_spill';
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Aggregations, sorts, and window functions run out-of-core: slower, but bounded.
|
|
46
|
+
- Bigger than RAM, DataFrame-shaped: Polars streaming (`collect(engine="streaming")` on a
|
|
47
|
+
lazy plan) in the resident kernel — or a uv one-shot on kernel-less harnesses.
|
|
48
|
+
- Manual chunked loops (read N rows, process, repeat) are the last resort — the engines'
|
|
49
|
+
own out-of-core paths are faster and simpler than hand-rolled chunking.
|
|
50
|
+
|
|
51
|
+
## Remote, in place — move the query to the data
|
|
52
|
+
|
|
53
|
+
- Files behind http(s)/S3: DuckDB's httpfs extension reads Parquet and CSV remotely with
|
|
54
|
+
projection and predicate pushdown —
|
|
55
|
+
|
|
56
|
+
```sql
|
|
57
|
+
INSTALL httpfs; LOAD httpfs; -- one-time per environment
|
|
58
|
+
SELECT region, SUM(amount) FROM 'https://example.com/sales.parquet'
|
|
59
|
+
WHERE sale_date >= '2026-01-01' GROUP BY region;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Only matching row groups and referenced columns cross the network, not the file.
|
|
63
|
+
- Data on another machine you can execute on (a remote worker with more RAM, a box closer
|
|
64
|
+
to the data): run the query there and return the aggregate. A group-by result is
|
|
65
|
+
kilobytes; the source is gigabytes.
|
|
66
|
+
- Decision rule: result much smaller than data — move the query. Repeated local iteration
|
|
67
|
+
on one slice — move a pruned copy of that slice once, then work locally in memory.
|
|
68
|
+
|
|
69
|
+
## Hardware notes
|
|
70
|
+
|
|
71
|
+
- Both engines parallelize across all cores by default; leave that alone except on shared
|
|
72
|
+
machines (`SET threads = N` in DuckDB, `POLARS_MAX_THREADS` for Polars).
|
|
73
|
+
- Sustained swapping is the failure mode to avoid on memory-tight machines: when the probe
|
|
74
|
+
says the working set is close to free RAM, choose streaming, not hope.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Polars lane (resident Python kernel)
|
|
2
|
+
|
|
3
|
+
When the work is DataFrame-shaped, Polars is the right engine — and it runs in the resident
|
|
4
|
+
Python kernel by default. Kernels rarely ship polars/pyarrow preinstalled, so inject them
|
|
5
|
+
once per session; the install lands in a user cache keyed to the kernel's interpreter, and
|
|
6
|
+
the interpreter itself is never mutated (run with the skill directory as cwd, or spell out
|
|
7
|
+
the script's absolute path):
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import subprocess, sys
|
|
11
|
+
site = subprocess.run(["bash", "scripts/ensure-py-deps.sh", sys.executable],
|
|
12
|
+
capture_output=True, text=True, check=True).stdout.strip()
|
|
13
|
+
sys.path.insert(0, site)
|
|
14
|
+
import polars as pl
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
After this, Polars lives across cells like every other resident engine: lazy frames,
|
|
18
|
+
intermediate results, and the DuckDB handoff all persist with no per-call process cost.
|
|
19
|
+
|
|
20
|
+
## When Polars wins over DuckDB SQL
|
|
21
|
+
|
|
22
|
+
- Expression-chain transforms: many derived columns, per-column conditional logic, string
|
|
23
|
+
pipelines — `with_columns` chains read and optimize better than nested SQL SELECTs.
|
|
24
|
+
- Reshapes: `unpivot`/`pivot` beat SQL gymnastics.
|
|
25
|
+
- Larger-than-RAM pipelines: the streaming engine executes lazy plans in chunks.
|
|
26
|
+
- Window-heavy feature engineering with `over()`.
|
|
27
|
+
|
|
28
|
+
SQL-shaped work (joins, aggregation, ad-hoc questions) stays in DuckDB; mixed pipelines hand
|
|
29
|
+
off zero-copy (below) instead of forcing one engine to do everything.
|
|
30
|
+
|
|
31
|
+
## Current API (1.x) — older spellings fail or warn
|
|
32
|
+
|
|
33
|
+
Training data is full of the pre-1.0 API. Current names:
|
|
34
|
+
|
|
35
|
+
| Use | Not |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `pl.scan_csv` / `pl.scan_parquet` + `.collect()` | eager `read_*` on big files |
|
|
38
|
+
| `.group_by(...)` | `.groupby(...)` |
|
|
39
|
+
| `pl.len()` | `pl.count()` |
|
|
40
|
+
| `.collect(engine="streaming")` | `.collect(streaming=True)` |
|
|
41
|
+
| `.unpivot(...)` | `.melt(...)` |
|
|
42
|
+
|
|
43
|
+
Lazy first: `scan_*` builds a plan, pushes filters and projections down to the file read, and
|
|
44
|
+
executes once at `.collect()`. Eager `read_*` is for small files mutated interactively.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
out = (pl.scan_csv("data.csv")
|
|
48
|
+
.filter(pl.col("value") > 100)
|
|
49
|
+
.group_by("category")
|
|
50
|
+
.agg(pl.col("value").sum().alias("total"), pl.len().alias("n"))
|
|
51
|
+
.sort("total", descending=True)
|
|
52
|
+
.collect())
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Zero-copy handoff with DuckDB
|
|
56
|
+
|
|
57
|
+
Both engines speak Arrow, so mixed pipelines pay no serialization cost — all in-kernel:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import duckdb
|
|
61
|
+
df = duckdb.sql("SELECT * FROM 'orders.csv' o JOIN 'items.csv' i USING (id)").pl()
|
|
62
|
+
shaped = df.with_columns((pl.col("qty") * pl.col("price")).alias("rev"))
|
|
63
|
+
duckdb.register("shaped", shaped)
|
|
64
|
+
out = duckdb.sql("SELECT category, SUM(rev) AS total FROM shaped GROUP BY 1").pl()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- `.pl()` requires pyarrow — the injection above provides it; without it, it raises
|
|
68
|
+
`ModuleNotFoundError`.
|
|
69
|
+
- Never `.df()`: it requires pandas (raising without it), and pandas is banned and absent.
|
|
70
|
+
|
|
71
|
+
## Streaming past RAM
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
out = (pl.scan_parquet("huge.parquet")
|
|
75
|
+
.filter(pl.col("status") == "active")
|
|
76
|
+
.group_by("region").agg(pl.len())
|
|
77
|
+
.collect(engine="streaming"))
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Streaming executes lazy plans only — keep the plan lazy end-to-end, with no intermediate
|
|
81
|
+
`.collect()` breaking it into eager pieces.
|
|
82
|
+
|
|
83
|
+
## Kernel-less fallback (uv one-shot)
|
|
84
|
+
|
|
85
|
+
On a harness with no persistent kernel, the same code runs as one-shots — batch several
|
|
86
|
+
questions per process, since each invocation pays spawn plus imports:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
uv run --with duckdb --with polars --with pyarrow python -c "
|
|
90
|
+
import duckdb
|
|
91
|
+
import polars as pl
|
|
92
|
+
df = duckdb.sql(\"SELECT * FROM 'data.csv'\").pl()
|
|
93
|
+
print(df.group_by('category').agg(pl.len()).sort('category'))
|
|
94
|
+
"
|
|
95
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# uv Setup — Per-Platform
|
|
2
2
|
|
|
3
|
-
This skill
|
|
3
|
+
This skill's uv lane (kernel-less harnesses, isolated one-shots) runs through `uv run --with ...`, and `scripts/ensure-py-deps.sh` uses uv as its installer. If `uv --version` fails, set uv up with the automated scripts or the manual commands below, then verify.
|
|
4
4
|
|
|
5
5
|
## Automated (recommended)
|
|
6
6
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Visualization
|
|
2
|
+
|
|
3
|
+
A chart exists to answer a question at a glance. Render it with matplotlib (resident in most
|
|
4
|
+
Python kernels; `uv run --with matplotlib` otherwise), then look at it before delivering —
|
|
5
|
+
a chart nobody inspected is not evidence.
|
|
6
|
+
|
|
7
|
+
## When to chart
|
|
8
|
+
|
|
9
|
+
Chart when the user asked for one, and default to charting when the answer is a shape prose
|
|
10
|
+
cannot carry: a trend over time, a distribution, a comparison across many categories, a
|
|
11
|
+
relationship between variables. Skip the chart when a number or a five-row table answers the
|
|
12
|
+
question — decoration dilutes the answer.
|
|
13
|
+
|
|
14
|
+
## Chart type follows the question
|
|
15
|
+
|
|
16
|
+
| Question shape | Chart |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| How did X change over time? | line, datetime x-axis |
|
|
19
|
+
| Which categories are biggest? | horizontal bar, sorted by value |
|
|
20
|
+
| How is X distributed? | histogram (tune bin count) or box plot per group |
|
|
21
|
+
| Is X related to Y? | scatter; add a trend line only when it aids the eye |
|
|
22
|
+
| Composition of a whole? | stacked or 100% bar — pie only for four or fewer slices |
|
|
23
|
+
| Many series over time? | small multiples over one spaghetti chart |
|
|
24
|
+
|
|
25
|
+
## Quality bar — every chart
|
|
26
|
+
|
|
27
|
+
- Title states the finding ("Seoul overtook Busan in March"), not the dataset name.
|
|
28
|
+
- Axis labels carry units. Tick density stays readable: `fig.autofmt_xdate()` for dates,
|
|
29
|
+
rotate or abbreviate long category names.
|
|
30
|
+
- Size for the medium: inline chat reads well around `figsize=(10, 6)` at default dpi;
|
|
31
|
+
documents want `dpi=150` or more at export.
|
|
32
|
+
- `tight_layout()` (or `constrained_layout=True`) before saving — clipped labels are the
|
|
33
|
+
most common chart defect.
|
|
34
|
+
- Few series: label lines directly, or keep the legend inside empty plot space. Many
|
|
35
|
+
series: gray the context, color only the series that answers the question.
|
|
36
|
+
- The default color cycle is fine; avoid rainbow palettes and 3D. Sort categorical bars by
|
|
37
|
+
value, never alphabetically.
|
|
38
|
+
|
|
39
|
+
## CJK and other non-Latin text
|
|
40
|
+
|
|
41
|
+
Matplotlib's default font renders CJK as empty boxes (tofu). Set a fallback before plotting
|
|
42
|
+
whenever any label or title contains CJK:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import platform
|
|
46
|
+
import matplotlib
|
|
47
|
+
cjk = {"Darwin": "AppleGothic", "Windows": "Malgun Gothic"}.get(platform.system(), "Noto Sans CJK KR")
|
|
48
|
+
matplotlib.rcParams["font.family"] = [cjk, "DejaVu Sans"]
|
|
49
|
+
matplotlib.rcParams["axes.unicode_minus"] = False # keeps the minus sign rendering
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Output contract
|
|
53
|
+
|
|
54
|
+
1. Save a PNG next to the work: `plt.savefig(path, dpi=150, bbox_inches="tight")`.
|
|
55
|
+
2. Also render inline when the surface displays rich output (kernels usually do).
|
|
56
|
+
3. Report the file path together with the answer.
|
|
57
|
+
|
|
58
|
+
## Visual QA — mandatory
|
|
59
|
+
|
|
60
|
+
Open the produced image — kernel display, or the harness's image-reading surface — and
|
|
61
|
+
check four things: labels readable and unclipped, no tofu or mojibake, nothing overlapping,
|
|
62
|
+
and the chart actually shows the finding the title claims. A failed check means fix and
|
|
63
|
+
re-render, not ship with a caveat. This one pass catches nearly every chart defect;
|
|
64
|
+
skipping it is how tofu titles reach users.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Install @duckdb/node-api into a user-level cache (outside any repo) and print the
|
|
3
|
+
# absolute import path as the ONLY stdout line. Idempotent: re-runs reuse the install.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
log() { printf '[ensure-js-deps] %s\n' "$*" >&2; }
|
|
7
|
+
|
|
8
|
+
CACHE_DIR="${OMO_DATA_SCIENTIST_CACHE:-$HOME/.cache/omo-data-scientist}"
|
|
9
|
+
IMPORT_PATH="$CACHE_DIR/node_modules/@duckdb/node-api/lib/index.js"
|
|
10
|
+
|
|
11
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
12
|
+
log "bun is required (https://bun.sh); install it, or use the uv lane instead."
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [ ! -f "$IMPORT_PATH" ]; then
|
|
17
|
+
log "installing @duckdb/node-api into $CACHE_DIR"
|
|
18
|
+
mkdir -p "$CACHE_DIR"
|
|
19
|
+
[ -f "$CACHE_DIR/package.json" ] || printf '{"name":"omo-data-scientist-cache","private":true}\n' > "$CACHE_DIR/package.json"
|
|
20
|
+
(cd "$CACHE_DIR" && bun add @duckdb/node-api 1>&2)
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [ ! -f "$IMPORT_PATH" ]; then
|
|
24
|
+
log "install finished but $IMPORT_PATH is missing; inspect $CACHE_DIR"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
printf '%s\n' "$IMPORT_PATH"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Install polars + pyarrow for a given Python interpreter into a user-level cache
|
|
3
|
+
# (never mutating the interpreter itself) and print the site directory as the ONLY
|
|
4
|
+
# stdout line. Idempotent: re-runs reuse the install. arg1 = python executable
|
|
5
|
+
# (default: python3); pass the kernel's sys.executable for kernel use.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
log() { printf '[ensure-py-deps] %s\n' "$*" >&2; }
|
|
9
|
+
|
|
10
|
+
PYTHON_BIN="${1:-python3}"
|
|
11
|
+
|
|
12
|
+
if ! command -v uv >/dev/null 2>&1; then
|
|
13
|
+
log "uv is required (see references/uv-setup.md); install it first."
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
if ! command -v "$PYTHON_BIN" >/dev/null 2>&1 && [ ! -x "$PYTHON_BIN" ]; then
|
|
17
|
+
log "python executable not found: $PYTHON_BIN"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
TAG="$("$PYTHON_BIN" -c 'import sys; print(f"cp{sys.version_info[0]}{sys.version_info[1]}")')" \
|
|
22
|
+
|| { log "not a working python interpreter: $PYTHON_BIN"; exit 1; }
|
|
23
|
+
CACHE_DIR="${OMO_DATA_SCIENTIST_CACHE:-$HOME/.cache/omo-data-scientist}"
|
|
24
|
+
SITE_DIR="$CACHE_DIR/py-$TAG"
|
|
25
|
+
|
|
26
|
+
if [ ! -d "$SITE_DIR/polars" ] || [ ! -d "$SITE_DIR/pyarrow" ]; then
|
|
27
|
+
log "installing polars + pyarrow for $TAG into $SITE_DIR"
|
|
28
|
+
mkdir -p "$SITE_DIR"
|
|
29
|
+
uv pip install --python "$PYTHON_BIN" --target "$SITE_DIR" polars pyarrow 1>&2
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
if [ ! -d "$SITE_DIR/polars" ]; then
|
|
33
|
+
log "install finished but $SITE_DIR/polars is missing; inspect $SITE_DIR"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
printf '%s\n' "$SITE_DIR"
|
|
@@ -161,7 +161,9 @@ Record the answer in the journal; Phase 5 opens by turning it into `design-spec.
|
|
|
161
161
|
|
|
162
162
|
## Phase 1 — Saturation wave
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
**When the user asked for MASS research, the wave is sized by the topic's angles, not by the roster ceiling.** "mass ulw research", "mulw research", "ulw mass research" — in any language — order over-collection that a team of 8 cannot produce. Where the harness has a dependency-graph surface (the `mass-ulw` skill and its `references/planning.md`), run collection through it instead: a 60+ node opening wave covering every angle the topic has, routed across the whole difficulty ladder in one graph — mechanical sweeps and per-item harvest batches on the cheapest tier, judgment-shaped angles a step up, cross-territory angles a step above that, and the deepest tier reserved for genuinely hairy contradictions. Each wave's EXPAND leads define the next wave's nodes, and the synthesis reduces through several parallel architect-tier nodes into ONE architect-tier reducer (the strongest reasoning tier substitutes wherever no architect tier exists). Absent that surface, hold the same shape with background workers in batched waves. Everything else in this skill still binds: the format gate, the journal, the claim graph, the convergence rules, and the delivery gates.
|
|
165
|
+
|
|
166
|
+
Otherwise launch the entire first wave in one turn — every axis at once, as team members if you formed a team, else as background workers. Sequential launches and "start with one and see" defeat the mode.
|
|
165
167
|
|
|
166
168
|
Scaling floor — more angles always justify more workers:
|
|
167
169
|
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
# Common Scenarios with Tool Selection
|
|
2
|
-
|
|
3
|
-
## Scenario 1: Simple Calculation
|
|
4
|
-
|
|
5
|
-
**Decision: Python (always)**
|
|
6
|
-
|
|
7
|
-
```python
|
|
8
|
-
uv run --with numpy python -c "
|
|
9
|
-
import numpy as np
|
|
10
|
-
result = np.sum([1, 2, 3, 4, 5])
|
|
11
|
-
print(f'Result: {result}')
|
|
12
|
-
"
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Scenario 2: CSV Quick Analysis
|
|
16
|
-
|
|
17
|
-
**Decision: DuckDB (direct query, no memory load)**
|
|
18
|
-
|
|
19
|
-
```python
|
|
20
|
-
uv run --with numpy --with duckdb python -c "
|
|
21
|
-
import duckdb
|
|
22
|
-
result = duckdb.sql('''
|
|
23
|
-
SELECT * FROM 'data.csv'
|
|
24
|
-
LIMIT 10
|
|
25
|
-
''').pl()
|
|
26
|
-
print(result)
|
|
27
|
-
"
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Scenario 3: Filter + Sort on Large Dataset
|
|
31
|
-
|
|
32
|
-
**Decision: Polars (128x faster filtering, 12x faster sorting)**
|
|
33
|
-
|
|
34
|
-
```python
|
|
35
|
-
uv run --with numpy --with polars python -c "
|
|
36
|
-
import polars as pl
|
|
37
|
-
result = (
|
|
38
|
-
pl.scan_csv('large.csv')
|
|
39
|
-
.filter(pl.col('value') > 1000)
|
|
40
|
-
.sort('value', descending=True)
|
|
41
|
-
.head(100)
|
|
42
|
-
.collect()
|
|
43
|
-
)
|
|
44
|
-
print(result)
|
|
45
|
-
"
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Scenario 4: Multi-Table Join + Aggregation
|
|
49
|
-
|
|
50
|
-
**Decision: DuckDB (best for joins and aggregations)**
|
|
51
|
-
|
|
52
|
-
```python
|
|
53
|
-
uv run --with numpy --with duckdb python -c "
|
|
54
|
-
import duckdb
|
|
55
|
-
result = duckdb.sql('''
|
|
56
|
-
SELECT
|
|
57
|
-
a.category,
|
|
58
|
-
COUNT(*) as count,
|
|
59
|
-
SUM(b.amount) as total
|
|
60
|
-
FROM 'table1.csv' a
|
|
61
|
-
JOIN 'table2.csv' b ON a.id = b.id
|
|
62
|
-
GROUP BY a.category
|
|
63
|
-
ORDER BY total DESC
|
|
64
|
-
''').pl()
|
|
65
|
-
print(result)
|
|
66
|
-
"
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Scenario 5: Data Exploration
|
|
70
|
-
|
|
71
|
-
**Decision: DuckDB for quick exploration**
|
|
72
|
-
|
|
73
|
-
```python
|
|
74
|
-
uv run --with numpy --with duckdb python -c "
|
|
75
|
-
import duckdb
|
|
76
|
-
|
|
77
|
-
# Show first few rows
|
|
78
|
-
print('**Sample Data**')
|
|
79
|
-
print(duckdb.sql('SELECT * FROM \"data.csv\" LIMIT 5').pl())
|
|
80
|
-
|
|
81
|
-
# Show summary statistics
|
|
82
|
-
print('\\n**Summary Statistics**')
|
|
83
|
-
print(duckdb.sql('DESCRIBE SELECT * FROM \"data.csv\"').pl())
|
|
84
|
-
|
|
85
|
-
# Show row count
|
|
86
|
-
print('\\n**Row Count**')
|
|
87
|
-
print(duckdb.sql('SELECT COUNT(*) as total_rows FROM \"data.csv\"').pl())
|
|
88
|
-
"
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Scenario 6: Time-Series Analysis
|
|
92
|
-
|
|
93
|
-
**Decision: DuckDB for aggregation + matplotlib for visualization**
|
|
94
|
-
|
|
95
|
-
```python
|
|
96
|
-
uv run --with numpy --with duckdb --with pyarrow --with matplotlib python -c "
|
|
97
|
-
import duckdb
|
|
98
|
-
import matplotlib.pyplot as plt
|
|
99
|
-
|
|
100
|
-
# Aggregate by date
|
|
101
|
-
result = duckdb.sql('''
|
|
102
|
-
SELECT
|
|
103
|
-
DATE_TRUNC('day', timestamp) as date,
|
|
104
|
-
COUNT(*) as count,
|
|
105
|
-
AVG(value) as avg_value
|
|
106
|
-
FROM 'timeseries.csv'
|
|
107
|
-
GROUP BY date
|
|
108
|
-
ORDER BY date
|
|
109
|
-
''').pl()
|
|
110
|
-
|
|
111
|
-
# Plot
|
|
112
|
-
plt.figure(figsize=(12, 6))
|
|
113
|
-
plt.subplot(2, 1, 1)
|
|
114
|
-
plt.plot(result['date'], result['count'])
|
|
115
|
-
plt.title('Daily Count')
|
|
116
|
-
|
|
117
|
-
plt.subplot(2, 1, 2)
|
|
118
|
-
plt.plot(result['date'], result['avg_value'])
|
|
119
|
-
plt.title('Daily Average Value')
|
|
120
|
-
|
|
121
|
-
plt.tight_layout()
|
|
122
|
-
plt.savefig('timeseries.png')
|
|
123
|
-
print('Saved to timeseries.png')
|
|
124
|
-
"
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Scenario 7: Complex Transformation
|
|
128
|
-
|
|
129
|
-
**Decision: Polars for efficient transformations**
|
|
130
|
-
|
|
131
|
-
```python
|
|
132
|
-
uv run --with numpy --with polars python -c "
|
|
133
|
-
import polars as pl
|
|
134
|
-
|
|
135
|
-
result = (
|
|
136
|
-
pl.scan_csv('data.csv')
|
|
137
|
-
.with_columns([
|
|
138
|
-
# Create new calculated columns
|
|
139
|
-
(pl.col('price') * pl.col('quantity')).alias('total'),
|
|
140
|
-
pl.col('date').str.strptime(pl.Date, '%Y-%m-%d').alias('parsed_date'),
|
|
141
|
-
pl.col('name').str.to_uppercase().alias('upper_name'),
|
|
142
|
-
])
|
|
143
|
-
.filter(pl.col('total') > 100)
|
|
144
|
-
.select(['parsed_date', 'upper_name', 'total'])
|
|
145
|
-
.collect()
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
print(result)
|
|
149
|
-
"
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## Scenario 8: Large File Processing
|
|
153
|
-
|
|
154
|
-
**Decision: Polars streaming mode**
|
|
155
|
-
|
|
156
|
-
```python
|
|
157
|
-
uv run --with numpy --with polars python -c "
|
|
158
|
-
import polars as pl
|
|
159
|
-
|
|
160
|
-
# Process file larger than RAM
|
|
161
|
-
result = (
|
|
162
|
-
pl.scan_csv('huge_file.csv')
|
|
163
|
-
.filter(pl.col('active') == True)
|
|
164
|
-
.groupby('category')
|
|
165
|
-
.agg([
|
|
166
|
-
pl.count().alias('count'),
|
|
167
|
-
pl.sum('amount').alias('total'),
|
|
168
|
-
pl.mean('amount').alias('average'),
|
|
169
|
-
])
|
|
170
|
-
.collect(streaming=True) # Streaming mode
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
print(result)
|
|
174
|
-
print(f'\\nProcessed {result[\"count\"].sum():,} rows')
|
|
175
|
-
"
|
|
176
|
-
```
|