pi-eda 0.2.0 → 0.2.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Shivam V Verma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -13,10 +13,16 @@ The [`pi-subagents`](https://www.npmjs.com/package/pi-subagents) package, for
13
13
  custom-agent support and the `/skill:pi-subagents` dispatch pattern the skills
14
14
  reference.
15
15
 
16
+ For plot review, use Pi interactively in a terminal with inline-image support.
17
+ Leave Pi's default `terminal.showImages` setting enabled so `show` can render
18
+ figures to the user without adding their pixels to model context.
19
+
16
20
  ## Install
17
21
 
22
+ Install dependencies & this package:
23
+
18
24
  ```bash
19
- pi install git:github.com/shivam-v-verma/pi-eda
25
+ pi install npm:pi-subagents
20
26
  pi install npm:pi-eda
21
27
  ```
22
28
 
@@ -18,6 +18,7 @@
18
18
 
19
19
  import { readFile } from "node:fs/promises";
20
20
  import { resolve } from "node:path";
21
+ import { fileURLToPath } from "node:url";
21
22
  import type {
22
23
  ExtensionAPI,
23
24
  ExtensionContext,
@@ -35,9 +36,14 @@ import {
35
36
  handleStatus,
36
37
  handleClear,
37
38
  formatWidgetText,
39
+ resolveGraphPath,
38
40
  reconstructFromBranch,
39
41
  } from "./lib/eda-workflow-tracker-core.js";
40
42
 
43
+ const bundledGraphPath = fileURLToPath(
44
+ new URL("../skills/exploring-data/workflow.graph.json", import.meta.url),
45
+ );
46
+
41
47
  const WorkflowTrackerParams = Type.Object({
42
48
  action: Type.Union(
43
49
  [
@@ -51,7 +57,7 @@ const WorkflowTrackerParams = Type.Object({
51
57
  graphPath: Type.Optional(
52
58
  Type.String({
53
59
  description:
54
- "Path to a workflow.graph.json ({ start, edges }) (for init)",
60
+ "Path to a workflow.graph.json ({ start, edges }); defaults to the bundled EDA graph (for init)",
55
61
  }),
56
62
  ),
57
63
  to: Type.Optional(
@@ -134,17 +140,12 @@ export default function (pi: ExtensionAPI) {
134
140
 
135
141
  switch (params.action) {
136
142
  case "init": {
137
- if (!params.graphPath) {
138
- result = {
139
- graphPath,
140
- current,
141
- next: [],
142
- error: "graphPath required for init",
143
- };
144
- break;
145
- }
143
+ const selectedGraphPath = resolveGraphPath(
144
+ params.graphPath,
145
+ bundledGraphPath,
146
+ );
146
147
  try {
147
- const parsed = await loadGraphFile(ctx.cwd, params.graphPath);
148
+ const parsed = await loadGraphFile(ctx.cwd, selectedGraphPath);
148
149
  if (!isValidGraph(parsed)) {
149
150
  throw new Error("expected { start: string, edges: object }");
150
151
  }
@@ -154,11 +155,11 @@ export default function (pi: ExtensionAPI) {
154
155
  graphPath,
155
156
  current,
156
157
  next: [],
157
- error: `failed to load graph at "${params.graphPath}": ${(err as Error).message}`,
158
+ error: `failed to load graph at "${selectedGraphPath}": ${(err as Error).message}`,
158
159
  };
159
160
  break;
160
161
  }
161
- result = handleInit(graph, params.graphPath);
162
+ result = handleInit(graph, selectedGraphPath);
162
163
  graphPath = result.graphPath;
163
164
  current = result.current;
164
165
  updateWidget(ctx);
@@ -196,6 +197,10 @@ export default function (pi: ExtensionAPI) {
196
197
  }
197
198
  case "status": {
198
199
  result = handleStatus(graph, current, graphPath);
200
+ if (!graph && current) {
201
+ result.error =
202
+ "graph not loaded -- current state was reconstructed but the graph file is unavailable; call init to reload";
203
+ }
199
204
  break;
200
205
  }
201
206
  case "clear": {
@@ -11,15 +11,38 @@ export interface WorkflowGraph {
11
11
  edges: Record<string, string[]>;
12
12
  }
13
13
 
14
- /** True if `value` has the { start: string, edges: object } shape a WorkflowGraph requires. */
14
+ /** True if `value` has a known start node and string-array edges. */
15
15
  export function isValidGraph(value: unknown): value is WorkflowGraph {
16
16
  if (typeof value !== "object" || value === null) return false;
17
17
  const candidate = value as Record<string, unknown>;
18
- return (
19
- typeof candidate.start === "string" &&
20
- typeof candidate.edges === "object" &&
21
- candidate.edges !== null
22
- );
18
+ if (
19
+ typeof candidate.start !== "string" ||
20
+ typeof candidate.edges !== "object" ||
21
+ candidate.edges === null ||
22
+ Array.isArray(candidate.edges)
23
+ ) {
24
+ return false;
25
+ }
26
+
27
+ const edges = candidate.edges as Record<string, unknown>;
28
+ if (
29
+ !Object.values(edges).every(
30
+ (targets) =>
31
+ Array.isArray(targets) &&
32
+ targets.every((target) => typeof target === "string"),
33
+ )
34
+ ) {
35
+ return false;
36
+ }
37
+
38
+ return allNodes(edges as Record<string, string[]>).has(candidate.start);
39
+ }
40
+
41
+ export function resolveGraphPath(
42
+ graphPath: string | undefined,
43
+ bundledGraphPath: string,
44
+ ): string {
45
+ return graphPath ?? bundledGraphPath;
23
46
  }
24
47
 
25
48
  export interface WorkflowTrackerDetails {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Pure logic for the `show` extension, split out for unit testing without
3
+ * needing a live ExtensionAPI/TUI (same pattern as eda-workflow-tracker-core.ts).
4
+ */
5
+
6
+ import { extname, resolve } from "node:path";
7
+
8
+ const MEDIA_TYPES: Record<string, string> = {
9
+ ".png": "image/png",
10
+ ".jpg": "image/jpeg",
11
+ ".jpeg": "image/jpeg",
12
+ ".gif": "image/gif",
13
+ ".webp": "image/webp",
14
+ ".bmp": "image/bmp",
15
+ };
16
+
17
+ export function resolveMediaType(path: string): string | undefined {
18
+ return MEDIA_TYPES[extname(path).toLowerCase()];
19
+ }
20
+
21
+ export function resolveImagePath(rawPath: string, cwd: string): string {
22
+ const path = rawPath.startsWith("@") ? rawPath.slice(1) : rawPath;
23
+ return resolve(cwd, path);
24
+ }
@@ -9,19 +9,10 @@
9
9
  */
10
10
 
11
11
  import { readFile, stat } from "node:fs/promises";
12
- import { extname, resolve } from "node:path";
13
12
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
14
13
  import { Image, Text } from "@earendil-works/pi-tui";
15
14
  import { Type } from "typebox";
16
-
17
- const MEDIA_TYPES: Record<string, string> = {
18
- ".png": "image/png",
19
- ".jpg": "image/jpeg",
20
- ".jpeg": "image/jpeg",
21
- ".gif": "image/gif",
22
- ".webp": "image/webp",
23
- ".bmp": "image/bmp",
24
- };
15
+ import { resolveImagePath, resolveMediaType } from "./lib/show-core.js";
25
16
 
26
17
  interface ShowDetails {
27
18
  path: string;
@@ -48,12 +39,9 @@ export default function (pi: ExtensionAPI) {
48
39
  }),
49
40
 
50
41
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
51
- const rawPath = params.path.startsWith("@")
52
- ? params.path.slice(1)
53
- : params.path;
54
- const absolutePath = resolve(ctx.cwd, rawPath);
42
+ const absolutePath = resolveImagePath(params.path, ctx.cwd);
55
43
 
56
- const mediaType = MEDIA_TYPES[extname(absolutePath).toLowerCase()];
44
+ const mediaType = resolveMediaType(absolutePath);
57
45
  if (!mediaType) {
58
46
  return {
59
47
  content: [
@@ -107,7 +95,7 @@ export default function (pi: ExtensionAPI) {
107
95
  }
108
96
  return new Image(details.base64, details.mediaType, theme, {
109
97
  maxWidthCells: 80,
110
- maxHeightCells: 24,
98
+ maxHeightCells: 36,
111
99
  });
112
100
  },
113
101
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-eda",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Pi tooling for interactive exploratory data analysis.",
5
5
  "license": "MIT",
6
6
  "author": "Shivam V Verma",
@@ -16,6 +16,7 @@
16
16
  ],
17
17
  "files": [
18
18
  "README.md",
19
+ "LICENSE",
19
20
  "agents",
20
21
  "eda.mplstyle.example",
21
22
  "extensions",
@@ -25,9 +25,8 @@ what the figure shows, not a conclusion dressed as a finding.
25
25
 
26
26
  If using this skill, you **must** first:
27
27
 
28
- - Run `init` on `eda-workflow-tracker` with `graphPath:
29
- "skills/exploring-data/workflow.graph.json"` for guidance on what stage you
30
- should be working on.
28
+ - Run `init` on `eda-workflow-tracker` for guidance on what stage you should be
29
+ working on.
31
30
  - Read `workflow.md` for instructions for each stage.
32
31
 
33
32
  **Always call `advance` on the `eda-workflow-tracker` when done with a
@@ -7,8 +7,10 @@ wanted).
7
7
 
8
8
  ```text
9
9
  eda/
10
+ .gitignore # containing `outputs/`
10
11
  index.md # landing pad for readers -- what's been done,
11
12
  # links to each report below
13
+ outputs/ # gitignored data/caches, can be shared across analyses
12
14
  customer_churn.md # a report -- lives directly under eda/
13
15
  customer_churn/ # paired folder, same stem as the report file
14
16
  scripts/
@@ -35,6 +37,9 @@ eda/
35
37
  - Shared code (data loading, plotting helpers, constants) goes in an
36
38
  unnumbered `common_<topic>.py` and gets imported from there instead --
37
39
  never import one numbered script from another.
40
+ - `eda/outputs/` is a top-level, shared cache. Any script can read from or
41
+ write to it. No substructure required inside it. Scripts that produce
42
+ data caches should check-or-recompute so it can be repopulated if needed.
38
43
 
39
44
  ## Common Mistakes
40
45
 
@@ -10,8 +10,9 @@ per-type rule that hasn't been stated.
10
10
 
11
11
  - Generate figures with matplotlib, saved as PNG, following the conventions
12
12
  below.
13
- - Apply the user's `eda.mplstyle` at the top of the script
14
- (`plt.style.use("eda")`) and respect user defaults.
13
+ - If `~/.config/matplotlib/stylelib/eda.mplstyle` exists, apply it at the top
14
+ of the script (`plt.style.use("eda")`) and respect user defaults. Otherwise
15
+ skip `plt.style.use` and fall back to Defaults below.
15
16
  - Visually inspect every figure with the `read` tool before treating it as a
16
17
  result -- never make hypotheses from a figure you haven't looked at.
17
18
  - `read` is for your own inspection. When you're ready to show the figure to
@@ -30,10 +31,8 @@ per-type rule that hasn't been stated.
30
31
  If the user has not set up `eda.mplstyle` or has not offered opinions
31
32
  on plot style, use these defaults:
32
33
 
33
- - Colors: `eda.mplstyle` (see the repo-root `eda.mplstyle.example`) defaults to
34
- `lightsteelblue` for one color and `Blues`
35
- for a colormap -- pick something else only if needed to convey a point, e.g.
36
- categorical comparison.
34
+ - Colors: `lightsteelblue` for one color and `Blues` for a colormap -- pick
35
+ something else only if needed to convey a point, e.g. categorical comparison.
37
36
  - Gridlines: none. Spines / ticks: matplotlib defaults -- don't add or strip
38
37
  either.
39
38
  - Titles: Be brief -- words must earn their space. Detailed descriptions belong
@@ -5,13 +5,11 @@
5
5
  "Peek at the data": ["Propose/ask a question"],
6
6
  "Propose/ask a question": ["Build script/plot"],
7
7
  "Build script/plot": ["Light or heavy?"],
8
- "Light or heavy?": ["Reread script", "Dispatch script-checker"],
9
- "Reread script": ["Build script/plot", "Run now, this session"],
8
+ "Light or heavy?": ["Reread and run script", "Dispatch script-checker"],
9
+ "Reread and run script": ["Build script/plot", "Summarize the answer"],
10
10
  "Dispatch script-checker": ["Build script/plot", "Dispatch job-runner"],
11
- "Run now, this session": ["Summarize the answer"],
12
11
  "Dispatch job-runner": ["Summarize the answer"],
13
- "Summarize the answer": ["Continue analysis or lock in?"],
14
- "Continue analysis or lock in?": [
12
+ "Summarize the answer": [
15
13
  "Propose/ask a question",
16
14
  "/skill:finishing-exploratory-data-analysis"
17
15
  ]
@@ -7,7 +7,7 @@ exactly that stage of work; and then call `advance` before starting the next
7
7
  stage. Call `status` whenever you need to know where you are instead of holding
8
8
  it in your head.
9
9
 
10
- At the end of every turn, ask yourself if you have accomplished the exit
10
+ At the end of every turn, ask yourself if you have accomplished the exit
11
11
  condition to `advance` to the next stage.
12
12
 
13
13
  - If yes, `advance` should **always** be your first tool call.
@@ -28,10 +28,10 @@ condition to `advance` to the next stage.
28
28
  - Expect the scope to narrow or shift as the data yields a story during
29
29
  this analysis. This step is purely for you to ground yourself.
30
30
  - You should **never** be running code at this stage. If the user has
31
- already offered specific questions/approaches, you must confirm your
32
- understanding and then `advance`.
31
+ already offered a specific question/approach, it establishes the scope but
32
+ you must confirm a report name before advancing.
33
33
  - `advance` condition: the user has confirmed the scope of the analysis and has
34
- given a report name; or the user has already requested a specific question.
34
+ given a report name.
35
35
 
36
36
  ### `Peek at the data`
37
37
 
@@ -94,9 +94,9 @@ We want to increase iteration speed and not waste user time.
94
94
  treating as heavy."
95
95
  - `advance` condition: you understand the computational cost of the script.
96
96
 
97
- ### `Reread script`
97
+ ### `Reread and run script`
98
98
 
99
- Light path: Reread the script as a sanity check.
99
+ Light path: reread the script as a sanity check, then run it in this session.
100
100
 
101
101
  - Does it match the user's stated intent/framing, does each nontrivial
102
102
  transform do what it should?
@@ -105,13 +105,10 @@ Light path: Reread the script as a sanity check.
105
105
  - This is a deliberately lower bar than the heavy path. A subtle mistake here
106
106
  can also be caught downstream in `/skill:finishing-exploratory-data-analysis`.
107
107
  We just want to sanity check here.
108
- - `advance` condition: you have reread the script and haven't found issues.
109
-
110
- ### `Run now, this session`
111
-
112
- Light path continuation: run the smoke-tested script/plot in this session.
113
-
114
- - `advance` condition: you have run the script successfully on the data.
108
+ - Once the reread finds no issues, run the smoke-tested script/plot in this
109
+ session.
110
+ - `advance` condition: you have reread the script, found no issues, and run
111
+ it successfully on the data.
115
112
 
116
113
  ### `Dispatch script-checker`
117
114
 
@@ -149,14 +146,10 @@ Heavy path continuation: launch the `job-runner` only once the
149
146
  - `show` the figure(s) to the user, then summarize briefly with bullets.
150
147
  - Core principle: the user decides the interpretation. Continue interrogating
151
148
  the user until it makes sense to you and is consistent with the dataset.
152
- - `advance` condition: you understand the user's interpretation of the figure.
153
-
154
- ### `Continue analysis or lock in?`
155
-
156
- - Ask the user whether to continue with another question or lock in the
157
- analysis; keep interrogating until they pick one. "Next question" loops back
158
- to `Propose/ask a question`, "finish analysis" exits to the finishing skill.
159
- - `advance` condition: the user has decided to continue the session or finish it.
149
+ - Once the user makes a clear interpretation, ask whether to continue with another
150
+ question or lock in the analysis. Keep interrogating until they pick one.
151
+ - `advance` condition: you understand the user's interpretation of the
152
+ figure, and the user has decided to continue the session or finish it.
160
153
 
161
154
  ### `/skill:finishing-exploratory-data-analysis`
162
155
 
@@ -178,7 +171,7 @@ Figure/analysis seems meaningful, ready to write up or commit.
178
171
  | Silently skipping the smoke test on a "just read cache and plot" script | Only skip for zero-computation cached-read-and-plot, and state that justification explicitly |
179
172
  | Trusting a sanity check that only ran on an easy/symmetric case | Pick a case that could actually have failed, especially for matching/ordering/canonicalization logic |
180
173
  | Checking that a smoke test ran fast/clean without looking at what it produced | Glance at the actual output values -- a clean run on garbage isn't a pass |
181
- | Running a light-path script without rereading it first because it's "just quick" | Reread against the stated intent/framing before running -- speed doesn't waive the check, it just changes who does it |
174
+ | Running a light-path script without rereading it first because it's "just quick" | Reread against the stated intent/framing before running -- speed doesn't waive the check, it just changes who does it. Both happen in the same `Reread and run script` stage, but the reread must come first |
182
175
  | Launching a heavy scale-up "while the `script-checker` runs in the background" instead of waiting for it | Confirm the `script-checker` came back clean before launching -- don't launch and plan to fold feedback in later |
183
176
  | Letting 2+ small edits pile up on a heavy-path script since the last `script-checker` dispatch because none felt big enough alone | Dispatch again once 2+ edits have landed |
184
177
  | Treating a mid-work `script-checker` pass as covering the finishing skill's lock-in checkpoint | Different gate, different artifact -- a pending or completed `script-checker` pass never substitutes for it |