pi-eda 0.2.0 → 0.2.1
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/README.md +7 -1
- package/extensions/eda-workflow-tracker.ts +14 -13
- package/extensions/lib/eda-workflow-tracker-core.ts +29 -6
- package/extensions/lib/show-core.ts +24 -0
- package/extensions/show.ts +3 -15
- package/package.json +1 -1
- package/skills/exploring-data/SKILL.md +2 -3
- package/skills/exploring-data/directory-structure.md +5 -0
- package/skills/exploring-data/plotting.md +5 -6
- package/skills/exploring-data/workflow.graph.json +2 -3
- package/skills/exploring-data/workflow.md +11 -14
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
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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,
|
|
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 "${
|
|
158
|
+
error: `failed to load graph at "${selectedGraphPath}": ${(err as Error).message}`,
|
|
158
159
|
};
|
|
159
160
|
break;
|
|
160
161
|
}
|
|
161
|
-
result = handleInit(graph,
|
|
162
|
+
result = handleInit(graph, selectedGraphPath);
|
|
162
163
|
graphPath = result.graphPath;
|
|
163
164
|
current = result.current;
|
|
164
165
|
updateWidget(ctx);
|
|
@@ -11,15 +11,38 @@ export interface WorkflowGraph {
|
|
|
11
11
|
edges: Record<string, string[]>;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
/** True if `value` has
|
|
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
|
-
|
|
19
|
-
typeof candidate.start
|
|
20
|
-
typeof candidate.edges
|
|
21
|
-
candidate.edges
|
|
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
|
+
}
|
package/extensions/show.ts
CHANGED
|
@@ -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
|
|
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 =
|
|
44
|
+
const mediaType = resolveMediaType(absolutePath);
|
|
57
45
|
if (!mediaType) {
|
|
58
46
|
return {
|
|
59
47
|
content: [
|
package/package.json
CHANGED
|
@@ -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`
|
|
29
|
-
|
|
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
|
-
-
|
|
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: `
|
|
34
|
-
|
|
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,10 +5,9 @@
|
|
|
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", "
|
|
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
12
|
"Summarize the answer": ["Continue analysis or lock in?"],
|
|
14
13
|
"Continue analysis or lock in?": [
|
|
@@ -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
|
|
32
|
-
|
|
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
|
|
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:
|
|
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
|
-
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
|
@@ -178,7 +175,7 @@ Figure/analysis seems meaningful, ready to write up or commit.
|
|
|
178
175
|
| 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
176
|
| 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
177
|
| 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 |
|
|
178
|
+
| 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
179
|
| 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
180
|
| 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
181
|
| 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 |
|