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.
Files changed (112) hide show
  1. package/dist/cli/config-manager/parse-opencode-config-file.d.ts +2 -1
  2. package/dist/cli/doctor/checks/system-plugin.d.ts +4 -3
  3. package/dist/cli/index.js +59 -45
  4. package/dist/cli-node/index.js +82 -45
  5. package/dist/hooks/todo-continuation-enforcer/types.d.ts +1 -0
  6. package/dist/index.js +53 -28
  7. package/dist/shared/index.d.ts +1 -0
  8. package/dist/shared/legacy-plugin-warning.d.ts +2 -1
  9. package/dist/shared/plugin-entry-migrator.d.ts +4 -3
  10. package/dist/shared/plugin-entry-shape.d.ts +6 -0
  11. package/dist/skills/data-scientist/SKILL.md +99 -239
  12. package/dist/skills/data-scientist/references/execution-surfaces.md +91 -0
  13. package/dist/skills/data-scientist/references/placement.md +74 -0
  14. package/dist/skills/data-scientist/references/polars-lane.md +95 -0
  15. package/dist/skills/data-scientist/references/uv-setup.md +1 -1
  16. package/dist/skills/data-scientist/references/visualization.md +64 -0
  17. package/dist/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
  18. package/dist/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
  19. package/dist/skills/ulw-research/SKILL.md +3 -1
  20. package/dist/tui.d.ts +12 -0
  21. package/dist/tui.js +3 -3
  22. package/package.json +15 -15
  23. package/packages/lsp-core/src/request-context.test.ts +29 -0
  24. package/packages/lsp-core/src/request-context.ts +1 -1
  25. package/packages/lsp-daemon/dist/cli.js +1 -1
  26. package/packages/lsp-daemon/dist/client.js +1 -1
  27. package/packages/lsp-daemon/dist/index.js +1 -1
  28. package/packages/lsp-tools-mcp/dist/cli.js +1 -1
  29. package/packages/lsp-tools-mcp/dist/mcp.js +1 -1
  30. package/packages/lsp-tools-mcp/dist/request-context.js +1 -1
  31. package/packages/lsp-tools-mcp/dist/tools.js +1 -1
  32. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  33. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  34. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  35. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  36. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  37. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  39. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  40. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  41. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  42. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  43. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  44. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  45. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  46. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  48. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  49. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  50. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  51. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  52. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  53. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
  54. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  55. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  56. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  63. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  68. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  69. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  70. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  71. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  72. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  73. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  74. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  77. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  80. package/packages/omo-codex/plugin/package-lock.json +13 -13
  81. package/packages/omo-codex/plugin/package.json +1 -1
  82. package/packages/omo-codex/plugin/skills/data-scientist/SKILL.md +99 -239
  83. package/packages/omo-codex/plugin/skills/data-scientist/references/execution-surfaces.md +91 -0
  84. package/packages/omo-codex/plugin/skills/data-scientist/references/placement.md +74 -0
  85. package/packages/omo-codex/plugin/skills/data-scientist/references/polars-lane.md +95 -0
  86. package/packages/omo-codex/plugin/skills/data-scientist/references/uv-setup.md +1 -1
  87. package/packages/omo-codex/plugin/skills/data-scientist/references/visualization.md +64 -0
  88. package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
  89. package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
  90. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +3 -1
  91. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  92. package/packages/shared-skills/skills/data-scientist/SKILL.md +99 -239
  93. package/packages/shared-skills/skills/data-scientist/references/execution-surfaces.md +91 -0
  94. package/packages/shared-skills/skills/data-scientist/references/placement.md +74 -0
  95. package/packages/shared-skills/skills/data-scientist/references/polars-lane.md +95 -0
  96. package/packages/shared-skills/skills/data-scientist/references/uv-setup.md +1 -1
  97. package/packages/shared-skills/skills/data-scientist/references/visualization.md +64 -0
  98. package/packages/shared-skills/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
  99. package/packages/shared-skills/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
  100. package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -1
  101. package/dist/skills/data-scientist/references/common-scenarios.md +0 -176
  102. package/dist/skills/data-scientist/references/execution-templates.md +0 -197
  103. package/dist/skills/data-scientist/references/integration-patterns.md +0 -153
  104. package/dist/skills/data-scientist/references/performance-benchmarks.md +0 -37
  105. package/packages/omo-codex/plugin/skills/data-scientist/references/common-scenarios.md +0 -176
  106. package/packages/omo-codex/plugin/skills/data-scientist/references/execution-templates.md +0 -197
  107. package/packages/omo-codex/plugin/skills/data-scientist/references/integration-patterns.md +0 -153
  108. package/packages/omo-codex/plugin/skills/data-scientist/references/performance-benchmarks.md +0 -37
  109. package/packages/shared-skills/skills/data-scientist/references/common-scenarios.md +0 -176
  110. package/packages/shared-skills/skills/data-scientist/references/execution-templates.md +0 -197
  111. package/packages/shared-skills/skills/data-scientist/references/integration-patterns.md +0 -153
  112. 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 runs every data operation through `uv run --with ...`. If `uv --version` fails, set uv up with the automated scripts or the manual commands below, then verify.
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
- 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.
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 languageorder 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
 
package/dist/tui.d.ts CHANGED
@@ -1,4 +1,16 @@
1
1
  import type { TuiPluginModule } from "@opencode-ai/plugin/tui";
2
+ type SidebarSlotRegistration<Node> = {
3
+ readonly order: number;
4
+ readonly slots: {
5
+ readonly sidebar_content: () => Node | (() => Node);
6
+ };
7
+ };
8
+ type RegisterSidebarContentSlotInput<Node> = {
9
+ readonly registerSlot: (registration: SidebarSlotRegistration<Node>) => void;
10
+ readonly requestRender: () => void;
11
+ readonly renderSidebar: () => Node;
12
+ };
13
+ export declare function registerSidebarContentSlot<Node>({ registerSlot, requestRender, renderSidebar, }: RegisterSidebarContentSlotInput<Node>): void;
2
14
  export declare function handleTuiPollError(error: unknown, reportPollError?: (error: Error) => void): void;
3
15
  declare const module: TuiPluginModule;
4
16
  export default module;
package/dist/tui.js CHANGED
@@ -78502,7 +78502,6 @@ var sessionCategoryMap;
78502
78502
  var init_session_category_registry = __esm(() => {
78503
78503
  sessionCategoryMap = new Map;
78504
78504
  });
78505
-
78506
78505
  // packages/omo-opencode/src/shared/legacy-plugin-warning.ts
78507
78506
  var init_legacy_plugin_warning = __esm(() => {
78508
78507
  init_jsonc_parser2();
@@ -82080,7 +82079,7 @@ function registerSidebarContentSlot({
82080
82079
  registerSlot({
82081
82080
  order: 900,
82082
82081
  slots: {
82083
- sidebar_content: renderSidebar
82082
+ sidebar_content: () => renderSidebar
82084
82083
  }
82085
82084
  });
82086
82085
  requestRender();
@@ -82201,5 +82200,6 @@ var module = {
82201
82200
  var tui_default = module;
82202
82201
  export {
82203
82202
  tui_default as default,
82204
- handleTuiPollError
82203
+ handleTuiPollError,
82204
+ registerSidebarContentSlot
82205
82205
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode",
3
- "version": "5.0.0-beta.26",
3
+ "version": "5.0.0-beta.28",
4
4
  "description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
5
5
  "main": "./dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -181,7 +181,7 @@
181
181
  "zod": "^4.4.3"
182
182
  },
183
183
  "devDependencies": {
184
- "@code-yeongyu/senpi": "2026.8.28-2",
184
+ "@code-yeongyu/senpi": "2026.8.30",
185
185
  "@oh-my-opencode/agents-md-core": "workspace:*",
186
186
  "@oh-my-opencode/ast-grep-mcp": "workspace:*",
187
187
  "@oh-my-opencode/boulder-state": "workspace:*",
@@ -222,18 +222,18 @@
222
222
  "typescript": "^7.0.2"
223
223
  },
224
224
  "optionalDependencies": {
225
- "oh-my-opencode-darwin-arm64": "5.0.0-beta.26",
226
- "oh-my-opencode-darwin-x64": "5.0.0-beta.26",
227
- "oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.26",
228
- "oh-my-opencode-linux-arm64": "5.0.0-beta.26",
229
- "oh-my-opencode-linux-arm64-musl": "5.0.0-beta.26",
230
- "oh-my-opencode-linux-x64": "5.0.0-beta.26",
231
- "oh-my-opencode-linux-x64-baseline": "5.0.0-beta.26",
232
- "oh-my-opencode-linux-x64-musl": "5.0.0-beta.26",
233
- "oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.26",
234
- "oh-my-opencode-windows-arm64": "5.0.0-beta.26",
235
- "oh-my-opencode-windows-x64": "5.0.0-beta.26",
236
- "oh-my-opencode-windows-x64-baseline": "5.0.0-beta.26"
225
+ "oh-my-opencode-darwin-arm64": "5.0.0-beta.28",
226
+ "oh-my-opencode-darwin-x64": "5.0.0-beta.28",
227
+ "oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.28",
228
+ "oh-my-opencode-linux-arm64": "5.0.0-beta.28",
229
+ "oh-my-opencode-linux-arm64-musl": "5.0.0-beta.28",
230
+ "oh-my-opencode-linux-x64": "5.0.0-beta.28",
231
+ "oh-my-opencode-linux-x64-baseline": "5.0.0-beta.28",
232
+ "oh-my-opencode-linux-x64-musl": "5.0.0-beta.28",
233
+ "oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.28",
234
+ "oh-my-opencode-windows-arm64": "5.0.0-beta.28",
235
+ "oh-my-opencode-windows-x64": "5.0.0-beta.28",
236
+ "oh-my-opencode-windows-x64-baseline": "5.0.0-beta.28"
237
237
  },
238
238
  "overrides": {
239
239
  "@earendil-works/pi-agent-core": "0.84.2",
@@ -251,6 +251,6 @@
251
251
  "@code-yeongyu/comment-checker"
252
252
  ],
253
253
  "patchedDependencies": {
254
- "@code-yeongyu/senpi@2026.8.28-2": "patches/@code-yeongyu%2Fsenpi@2026.8.28-2.patch"
254
+ "@code-yeongyu/senpi@2026.8.30": "patches/@code-yeongyu%2Fsenpi@2026.8.30.patch"
255
255
  }
256
256
  }
@@ -168,4 +168,33 @@ describe("LspRequestContext", () => {
168
168
  capabilities: { installDecisionTool: true },
169
169
  });
170
170
  });
171
+
172
+ it("#given an injected LSP cwd #when standalone context is created #then it wins over process cwd without reading global env", () => {
173
+ const sessionRoot = tempRoot("lsp-context-session-");
174
+ const processRoot = tempRoot("lsp-context-process-");
175
+ const home = tempRoot("lsp-context-env-home-");
176
+
177
+ const context = createStandaloneMcpRequestContext({
178
+ env: {
179
+ LSP_TOOLS_MCP_CWD: sessionRoot,
180
+ },
181
+ homeDir: home,
182
+ });
183
+
184
+ expect(context.cwd).toBe(sessionRoot);
185
+ expect(context.projectConfigPaths).toEqual([join(sessionRoot, ".codex", "lsp-client.json")]);
186
+ expect(processRoot).not.toBe(context.cwd);
187
+ });
188
+
189
+ it("#given explicit cwd and an injected LSP cwd #when standalone context is created #then explicit cwd wins", () => {
190
+ const explicitRoot = tempRoot("lsp-context-explicit-");
191
+ const injectedRoot = tempRoot("lsp-context-injected-");
192
+
193
+ const context = createStandaloneMcpRequestContext({
194
+ cwd: explicitRoot,
195
+ env: { LSP_TOOLS_MCP_CWD: injectedRoot },
196
+ });
197
+
198
+ expect(context.cwd).toBe(explicitRoot);
199
+ });
171
200
  });
@@ -72,7 +72,7 @@ export function createStandaloneMcpRequestContext(
72
72
  input: StandaloneMcpRequestContextInput = {},
73
73
  ): LspRequestContext {
74
74
  const env = input.env ?? process.env;
75
- const cwd = canonicalCwd(input.cwd ?? process.cwd());
75
+ const cwd = canonicalCwd(input.cwd ?? env["LSP_TOOLS_MCP_CWD"] ?? process.cwd());
76
76
  const home = input.homeDir ?? homedir();
77
77
  const projectConfigPaths = translateProjectConfigEnv(env["LSP_TOOLS_MCP_PROJECT_CONFIG"], cwd);
78
78
  const userConfigPath = translateHomeConfigEnv(env["LSP_TOOLS_MCP_USER_CONFIG"], home, ".codex/lsp-client.json");
@@ -359,7 +359,7 @@ function contextCwd() {
359
359
  }
360
360
  function createStandaloneMcpRequestContext(input = {}) {
361
361
  const env = input.env ?? process.env;
362
- const cwd = canonicalCwd(input.cwd ?? process.cwd());
362
+ const cwd = canonicalCwd(input.cwd ?? env["LSP_TOOLS_MCP_CWD"] ?? process.cwd());
363
363
  const home = input.homeDir ?? homedir();
364
364
  const projectConfigPaths = translateProjectConfigEnv(env["LSP_TOOLS_MCP_PROJECT_CONFIG"], cwd);
365
365
  const userConfigPath = translateHomeConfigEnv(env["LSP_TOOLS_MCP_USER_CONFIG"], home, ".codex/lsp-client.json");
@@ -44,7 +44,7 @@ function contextCwd() {
44
44
  }
45
45
  function createStandaloneMcpRequestContext(input = {}) {
46
46
  const env = input.env ?? process.env;
47
- const cwd = canonicalCwd(input.cwd ?? process.cwd());
47
+ const cwd = canonicalCwd(input.cwd ?? env["LSP_TOOLS_MCP_CWD"] ?? process.cwd());
48
48
  const home = input.homeDir ?? homedir();
49
49
  const projectConfigPaths = translateProjectConfigEnv(env["LSP_TOOLS_MCP_PROJECT_CONFIG"], cwd);
50
50
  const userConfigPath = translateHomeConfigEnv(env["LSP_TOOLS_MCP_USER_CONFIG"], home, ".codex/lsp-client.json");
@@ -3075,7 +3075,7 @@ function contextCwd() {
3075
3075
  }
3076
3076
  function createStandaloneMcpRequestContext(input = {}) {
3077
3077
  const env = input.env ?? process.env;
3078
- const cwd = canonicalCwd(input.cwd ?? process.cwd());
3078
+ const cwd = canonicalCwd(input.cwd ?? env["LSP_TOOLS_MCP_CWD"] ?? process.cwd());
3079
3079
  const home = input.homeDir ?? homedir();
3080
3080
  const projectConfigPaths = translateProjectConfigEnv(env["LSP_TOOLS_MCP_PROJECT_CONFIG"], cwd);
3081
3081
  const userConfigPath = translateHomeConfigEnv(env["LSP_TOOLS_MCP_USER_CONFIG"], home, ".codex/lsp-client.json");
@@ -3397,7 +3397,7 @@ function contextEnv(key) {
3397
3397
  }
3398
3398
  function createStandaloneMcpRequestContext(input = {}) {
3399
3399
  const env = input.env ?? process.env;
3400
- const cwd = canonicalCwd(input.cwd ?? process.cwd());
3400
+ const cwd = canonicalCwd(input.cwd ?? env["LSP_TOOLS_MCP_CWD"] ?? process.cwd());
3401
3401
  const home = input.homeDir ?? homedir();
3402
3402
  const projectConfigPaths = translateProjectConfigEnv(env["LSP_TOOLS_MCP_PROJECT_CONFIG"], cwd);
3403
3403
  const userConfigPath = translateHomeConfigEnv(env["LSP_TOOLS_MCP_USER_CONFIG"], home, ".codex/lsp-client.json");
@@ -360,7 +360,7 @@ function contextEnv(key) {
360
360
  }
361
361
  function createStandaloneMcpRequestContext(input = {}) {
362
362
  const env = input.env ?? process.env;
363
- const cwd = canonicalCwd(input.cwd ?? process.cwd());
363
+ const cwd = canonicalCwd(input.cwd ?? env["LSP_TOOLS_MCP_CWD"] ?? process.cwd());
364
364
  const home = input.homeDir ?? homedir();
365
365
  const projectConfigPaths = translateProjectConfigEnv(env["LSP_TOOLS_MCP_PROJECT_CONFIG"], cwd);
366
366
  const userConfigPath = translateHomeConfigEnv(env["LSP_TOOLS_MCP_USER_CONFIG"], home, ".codex/lsp-client.json");
@@ -45,7 +45,7 @@ function contextEnv(key) {
45
45
  }
46
46
  function createStandaloneMcpRequestContext(input = {}) {
47
47
  const env = input.env ?? process.env;
48
- const cwd = canonicalCwd(input.cwd ?? process.cwd());
48
+ const cwd = canonicalCwd(input.cwd ?? env["LSP_TOOLS_MCP_CWD"] ?? process.cwd());
49
49
  const home = input.homeDir ?? homedir();
50
50
  const projectConfigPaths = translateProjectConfigEnv(env["LSP_TOOLS_MCP_PROJECT_CONFIG"], cwd);
51
51
  const userConfigPath = translateHomeConfigEnv(env["LSP_TOOLS_MCP_USER_CONFIG"], home, ".codex/lsp-client.json");
@@ -49,7 +49,7 @@ function contextEnv(key) {
49
49
  }
50
50
  function createStandaloneMcpRequestContext(input = {}) {
51
51
  const env = input.env ?? process.env;
52
- const cwd = canonicalCwd(input.cwd ?? process.cwd());
52
+ const cwd = canonicalCwd(input.cwd ?? env["LSP_TOOLS_MCP_CWD"] ?? process.cwd());
53
53
  const home = input.homeDir ?? homedir();
54
54
  const projectConfigPaths = translateProjectConfigEnv(env["LSP_TOOLS_MCP_PROJECT_CONFIG"], cwd);
55
55
  const userConfigPath = translateHomeConfigEnv(env["LSP_TOOLS_MCP_USER_CONFIG"], home, ".codex/lsp-client.json");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omo",
3
- "version": "5.0.0-beta.26",
3
+ "version": "5.0.0-beta.28",
4
4
  "description": "One Codex plugin namespace for Yeongyu's local Codex components.",
5
5
  "author": {
6
6
  "name": "Yeongyu Kim",
@@ -8,7 +8,7 @@
8
8
  "command": "node \"${PLUGIN_ROOT}/components/bootstrap/dist/cli.js\" hook session-start",
9
9
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\bootstrap.ps1\"",
10
10
  "timeout": 30,
11
- "statusMessage": "(OmO 5.0.0-beta.26) Checking Bootstrap Provisioning"
11
+ "statusMessage": "(OmO 5.0.0-beta.28) Checking Bootstrap Provisioning"
12
12
  }
13
13
  ]
14
14
  }