pi-eda 0.1.0 → 0.2.0
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 +1 -0
- package/agents/job-runner.md +21 -18
- package/extensions/eda-workflow-tracker.ts +266 -0
- package/extensions/lib/eda-workflow-tracker-core.ts +158 -0
- package/package.json +27 -5
- package/skills/exploring-data/SKILL.md +14 -6
- package/skills/exploring-data/workflow.graph.json +19 -0
- package/skills/exploring-data/workflow.md +177 -59
- package/skills/exploring-data/checking-and-running-scripts.md +0 -97
- package/skills/exploring-data/pre-code-checkpoints.md +0 -63
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ Pi skills and subagents for interactive exploratory data analysis.
|
|
|
5
5
|
- `skills/exploring-data`: iterative analysis & plotting
|
|
6
6
|
- `skills/finishing-exploratory-data-analysis`: locking in analysis into a wiki-style report
|
|
7
7
|
- `show` extension: renders a figure to the user in the TUI without spending tokens on it
|
|
8
|
+
- `eda-workflow-tracker` extension: tracks stage progress through `exploring-data`'s workflow graph
|
|
8
9
|
|
|
9
10
|
## Requires
|
|
10
11
|
|
package/agents/job-runner.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: job-runner
|
|
3
|
-
description: Launches
|
|
3
|
+
description: Launches a heavy/long-running analysis script under a memory cap and blocks on it
|
|
4
4
|
tools: bash, read
|
|
5
5
|
inheritProjectContext: true
|
|
6
6
|
---
|
|
@@ -12,23 +12,26 @@ that already happened before you were dispatched.
|
|
|
12
12
|
## What to do
|
|
13
13
|
|
|
14
14
|
- Confirm inputs before launching:
|
|
15
|
-
- The estimated runtime
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
15
|
+
- The estimated runtime (should be in the task you were given -- if
|
|
16
|
+
missing, ask the dispatching agent rather than guessing).
|
|
17
|
+
- Whether `systemd-run` is available (Linux only -- no shell-level
|
|
18
|
+
equivalent on macOS or Windows).
|
|
19
|
+
- Determine how much memory can be used for the process. Check
|
|
20
|
+
available memory with `free -m` and leave at least 1GB free.
|
|
21
|
+
- If `systemd-run` is available:
|
|
22
|
+
- Run in the foreground, blocked on completion using:
|
|
23
|
+
`systemd-run --scope -p MemoryMax=<budget> --user -- uv run <script>`.
|
|
24
|
+
- The cap is the safety net, so you should not:
|
|
25
|
+
- Poll for memory consumption
|
|
26
|
+
- Detach from the process
|
|
27
|
+
- If `systemd-run` is not available:
|
|
28
|
+
- Say so explicitly. There is no OS cap here, so you are the safety
|
|
29
|
+
net.
|
|
30
|
+
- Launch the script as a separate process, then poll memory yourself
|
|
31
|
+
against the memory budget.
|
|
32
|
+
- Do this as a *single* bash call with an internal sleep/check loop.
|
|
33
|
+
Kill and report if it crosses the budget to save token context.
|
|
34
|
+
Do not use separate agent turns here.
|
|
32
35
|
- Report back once it completes: success/failure, runtime, and where the
|
|
33
36
|
output landed. On failure, include the actual error, not just "it
|
|
34
37
|
failed."
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Tracker Extension
|
|
3
|
+
*
|
|
4
|
+
* A native pi tool for tracking progress through a named-state workflow
|
|
5
|
+
* graph (branches + loop-backs), not a flat todo list. Purely descriptive:
|
|
6
|
+
* nothing downstream enforces it, `advance` just validates the move is
|
|
7
|
+
* legal and remembers where the agent said it is.
|
|
8
|
+
*
|
|
9
|
+
* State is stored in tool result details for proper branching support,
|
|
10
|
+
* same restore-from-branch pattern as plan_tracker. Shows a one-line
|
|
11
|
+
* status widget with the raw current node name -- transparency for the
|
|
12
|
+
* user, not enforcement.
|
|
13
|
+
*
|
|
14
|
+
* Pure logic lives in lib/eda-workflow-tracker-core.ts for testability. Nested
|
|
15
|
+
* one level deep (not top-level extensions/, not an index.ts subdir) so pi's
|
|
16
|
+
* extension auto-discovery does not try to load it as a standalone extension.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFile } from "node:fs/promises";
|
|
20
|
+
import { resolve } from "node:path";
|
|
21
|
+
import type {
|
|
22
|
+
ExtensionAPI,
|
|
23
|
+
ExtensionContext,
|
|
24
|
+
} from "@earendil-works/pi-coding-agent";
|
|
25
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
26
|
+
import { Type, type Static } from "typebox";
|
|
27
|
+
import {
|
|
28
|
+
type WorkflowGraph,
|
|
29
|
+
type WorkflowTrackerDetails,
|
|
30
|
+
type BranchEntry,
|
|
31
|
+
type ActionResult,
|
|
32
|
+
isValidGraph,
|
|
33
|
+
handleInit,
|
|
34
|
+
handleAdvance,
|
|
35
|
+
handleStatus,
|
|
36
|
+
handleClear,
|
|
37
|
+
formatWidgetText,
|
|
38
|
+
reconstructFromBranch,
|
|
39
|
+
} from "./lib/eda-workflow-tracker-core.js";
|
|
40
|
+
|
|
41
|
+
const WorkflowTrackerParams = Type.Object({
|
|
42
|
+
action: Type.Union(
|
|
43
|
+
[
|
|
44
|
+
Type.Literal("init"),
|
|
45
|
+
Type.Literal("advance"),
|
|
46
|
+
Type.Literal("status"),
|
|
47
|
+
Type.Literal("clear"),
|
|
48
|
+
],
|
|
49
|
+
{ description: "Action to perform" },
|
|
50
|
+
),
|
|
51
|
+
graphPath: Type.Optional(
|
|
52
|
+
Type.String({
|
|
53
|
+
description:
|
|
54
|
+
"Path to a workflow.graph.json ({ start, edges }) (for init)",
|
|
55
|
+
}),
|
|
56
|
+
),
|
|
57
|
+
to: Type.Optional(
|
|
58
|
+
Type.String({
|
|
59
|
+
description: "Node name to advance to (for advance)",
|
|
60
|
+
}),
|
|
61
|
+
),
|
|
62
|
+
force: Type.Optional(
|
|
63
|
+
Type.Boolean({
|
|
64
|
+
description:
|
|
65
|
+
"Allow advancing to any valid node in the graph, not just ones adjacent to the current node (for advance)",
|
|
66
|
+
}),
|
|
67
|
+
),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export type WorkflowTrackerInput = Static<typeof WorkflowTrackerParams>;
|
|
71
|
+
|
|
72
|
+
export default function (pi: ExtensionAPI) {
|
|
73
|
+
let graphPath = "";
|
|
74
|
+
let graph: WorkflowGraph | undefined;
|
|
75
|
+
let current = "";
|
|
76
|
+
|
|
77
|
+
const loadGraphFile = async (cwd: string, path: string): Promise<unknown> => {
|
|
78
|
+
const raw = await readFile(resolve(cwd, path), "utf-8");
|
|
79
|
+
return JSON.parse(raw);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const reconstructState = async (ctx: ExtensionContext) => {
|
|
83
|
+
const state = reconstructFromBranch(
|
|
84
|
+
ctx.sessionManager.getBranch() as BranchEntry[],
|
|
85
|
+
);
|
|
86
|
+
graphPath = state.graphPath;
|
|
87
|
+
current = state.current;
|
|
88
|
+
graph = undefined;
|
|
89
|
+
if (!graphPath) return;
|
|
90
|
+
try {
|
|
91
|
+
const parsed = await loadGraphFile(ctx.cwd, graphPath);
|
|
92
|
+
if (isValidGraph(parsed)) graph = parsed;
|
|
93
|
+
} catch {
|
|
94
|
+
// leave graph undefined -- advance/status report it as not loaded
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const updateWidget = (ctx: ExtensionContext) => {
|
|
99
|
+
if (!ctx.hasUI) return;
|
|
100
|
+
const text = formatWidgetText(current);
|
|
101
|
+
if (text) {
|
|
102
|
+
ctx.ui.setWidget("eda-workflow-tracker", (_tui, theme) => {
|
|
103
|
+
return new Text(theme.fg("muted", text), 0, 0);
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
ctx.ui.setWidget("eda-workflow-tracker", undefined);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
for (const event of [
|
|
111
|
+
"session_start",
|
|
112
|
+
"session_switch",
|
|
113
|
+
"session_fork",
|
|
114
|
+
"session_tree",
|
|
115
|
+
] as const) {
|
|
116
|
+
pi.on(event, async (_event, ctx) => {
|
|
117
|
+
await reconstructState(ctx);
|
|
118
|
+
updateWidget(ctx);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
pi.registerTool({
|
|
123
|
+
name: "eda-workflow-tracker",
|
|
124
|
+
label: "Workflow Tracker",
|
|
125
|
+
description:
|
|
126
|
+
"Track the current state in a named-state workflow graph (branches/loop-backs, not a flat " +
|
|
127
|
+
"todo list). Actions: init (load a workflow.graph.json and set current to its start), " +
|
|
128
|
+
"advance (move to a node; must be adjacent to current unless force is set, in which case " +
|
|
129
|
+
"any valid node in the graph is allowed), status (show current state), clear (reset).",
|
|
130
|
+
parameters: WorkflowTrackerParams,
|
|
131
|
+
|
|
132
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
133
|
+
let result: ActionResult;
|
|
134
|
+
|
|
135
|
+
switch (params.action) {
|
|
136
|
+
case "init": {
|
|
137
|
+
if (!params.graphPath) {
|
|
138
|
+
result = {
|
|
139
|
+
graphPath,
|
|
140
|
+
current,
|
|
141
|
+
next: [],
|
|
142
|
+
error: "graphPath required for init",
|
|
143
|
+
};
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const parsed = await loadGraphFile(ctx.cwd, params.graphPath);
|
|
148
|
+
if (!isValidGraph(parsed)) {
|
|
149
|
+
throw new Error("expected { start: string, edges: object }");
|
|
150
|
+
}
|
|
151
|
+
graph = parsed;
|
|
152
|
+
} catch (err) {
|
|
153
|
+
result = {
|
|
154
|
+
graphPath,
|
|
155
|
+
current,
|
|
156
|
+
next: [],
|
|
157
|
+
error: `failed to load graph at "${params.graphPath}": ${(err as Error).message}`,
|
|
158
|
+
};
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
result = handleInit(graph, params.graphPath);
|
|
162
|
+
graphPath = result.graphPath;
|
|
163
|
+
current = result.current;
|
|
164
|
+
updateWidget(ctx);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "advance": {
|
|
168
|
+
if (!graph) {
|
|
169
|
+
result = {
|
|
170
|
+
graphPath,
|
|
171
|
+
current,
|
|
172
|
+
next: [],
|
|
173
|
+
error: "no graph loaded -- call init first",
|
|
174
|
+
};
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
if (!params.to) {
|
|
178
|
+
result = {
|
|
179
|
+
graphPath,
|
|
180
|
+
current,
|
|
181
|
+
next: [],
|
|
182
|
+
error: "to required for advance",
|
|
183
|
+
};
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
result = handleAdvance(
|
|
187
|
+
graph,
|
|
188
|
+
current,
|
|
189
|
+
params.to,
|
|
190
|
+
params.force ?? false,
|
|
191
|
+
graphPath,
|
|
192
|
+
);
|
|
193
|
+
current = result.current;
|
|
194
|
+
updateWidget(ctx);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case "status": {
|
|
198
|
+
result = handleStatus(graph, current, graphPath);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
case "clear": {
|
|
202
|
+
result = handleClear();
|
|
203
|
+
graphPath = result.graphPath;
|
|
204
|
+
current = result.current;
|
|
205
|
+
graph = undefined;
|
|
206
|
+
updateWidget(ctx);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
default: {
|
|
210
|
+
result = {
|
|
211
|
+
graphPath,
|
|
212
|
+
current,
|
|
213
|
+
next: [],
|
|
214
|
+
error: `unknown action: ${params.action as string}`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const details: WorkflowTrackerDetails = {
|
|
220
|
+
action: params.action,
|
|
221
|
+
graphPath: result.graphPath,
|
|
222
|
+
current: result.current,
|
|
223
|
+
next: result.next,
|
|
224
|
+
...(result.error ? { error: result.error } : {}),
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
content: [
|
|
229
|
+
{
|
|
230
|
+
type: "text",
|
|
231
|
+
text: result.error
|
|
232
|
+
? `Error: ${result.error}`
|
|
233
|
+
: `current: "${result.current}"; next: ${JSON.stringify(result.next)}`,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
details,
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
renderCall(args, theme) {
|
|
241
|
+
let text = theme.fg("toolTitle", theme.bold("eda-workflow-tracker "));
|
|
242
|
+
text += theme.fg("muted", args.action);
|
|
243
|
+
if (args.action === "advance" && args.to) {
|
|
244
|
+
text += ` -> ${theme.fg("accent", args.to)}`;
|
|
245
|
+
if (args.force) text += theme.fg("dim", " (force)");
|
|
246
|
+
}
|
|
247
|
+
return new Text(text, 0, 0);
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
renderResult(result, _options, theme) {
|
|
251
|
+
const details = result.details as WorkflowTrackerDetails | undefined;
|
|
252
|
+
if (!details) {
|
|
253
|
+
const text = result.content[0];
|
|
254
|
+
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
|
255
|
+
}
|
|
256
|
+
if (details.error) {
|
|
257
|
+
return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
258
|
+
}
|
|
259
|
+
return new Text(
|
|
260
|
+
theme.fg("success", "✓ ") + theme.fg("muted", details.current),
|
|
261
|
+
0,
|
|
262
|
+
0,
|
|
263
|
+
);
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Tracker Core Logic
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for a graph-shaped (not list-shaped) progress tracker:
|
|
5
|
+
* "current" is a node name in a caller-supplied edges map, advanced by
|
|
6
|
+
* naming the next node rather than by index. No pi dependencies.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface WorkflowGraph {
|
|
10
|
+
start: string;
|
|
11
|
+
edges: Record<string, string[]>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** True if `value` has the { start: string, edges: object } shape a WorkflowGraph requires. */
|
|
15
|
+
export function isValidGraph(value: unknown): value is WorkflowGraph {
|
|
16
|
+
if (typeof value !== "object" || value === null) return false;
|
|
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
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface WorkflowTrackerDetails {
|
|
26
|
+
action: "init" | "advance" | "status" | "clear";
|
|
27
|
+
graphPath: string;
|
|
28
|
+
current: string;
|
|
29
|
+
next: string[];
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ActionResult {
|
|
34
|
+
graphPath: string;
|
|
35
|
+
current: string;
|
|
36
|
+
next: string[];
|
|
37
|
+
error?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Nodes reachable in one step from `node`, or [] if none/no graph loaded. */
|
|
41
|
+
function nextFrom(graph: WorkflowGraph | undefined, node: string): string[] {
|
|
42
|
+
return graph?.edges[node] ?? [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function allNodes(edges: Record<string, string[]>): Set<string> {
|
|
46
|
+
const nodes = new Set<string>(Object.keys(edges));
|
|
47
|
+
for (const targets of Object.values(edges)) {
|
|
48
|
+
for (const target of targets) nodes.add(target);
|
|
49
|
+
}
|
|
50
|
+
return nodes;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Case-insensitive lookup: returns the canonically-cased node name, or undefined if none matches. */
|
|
54
|
+
function canonicalize(
|
|
55
|
+
candidates: Iterable<string>,
|
|
56
|
+
name: string,
|
|
57
|
+
): string | undefined {
|
|
58
|
+
const lower = name.toLowerCase();
|
|
59
|
+
for (const candidate of candidates) {
|
|
60
|
+
if (candidate.toLowerCase() === lower) return candidate;
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function handleInit(
|
|
66
|
+
graph: WorkflowGraph,
|
|
67
|
+
graphPath: string,
|
|
68
|
+
): ActionResult {
|
|
69
|
+
return {
|
|
70
|
+
graphPath,
|
|
71
|
+
current: graph.start,
|
|
72
|
+
next: nextFrom(graph, graph.start),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function handleAdvance(
|
|
77
|
+
graph: WorkflowGraph,
|
|
78
|
+
current: string,
|
|
79
|
+
to: string,
|
|
80
|
+
force: boolean,
|
|
81
|
+
graphPath = "",
|
|
82
|
+
): ActionResult {
|
|
83
|
+
const legalFromCurrent = graph.edges[current] ?? [];
|
|
84
|
+
|
|
85
|
+
if (!force) {
|
|
86
|
+
const canonical = canonicalize(legalFromCurrent, to);
|
|
87
|
+
if (!canonical) {
|
|
88
|
+
return {
|
|
89
|
+
graphPath,
|
|
90
|
+
current,
|
|
91
|
+
next: legalFromCurrent,
|
|
92
|
+
error: `"${to}" is not reachable from "${current}"; legal: ${JSON.stringify(legalFromCurrent)}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { graphPath, current: canonical, next: nextFrom(graph, canonical) };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const canonical = canonicalize(allNodes(graph.edges), to);
|
|
99
|
+
if (!canonical) {
|
|
100
|
+
return {
|
|
101
|
+
graphPath,
|
|
102
|
+
current,
|
|
103
|
+
next: legalFromCurrent,
|
|
104
|
+
error: `"${to}" is not a known node in this graph; legal: ${JSON.stringify(legalFromCurrent)}`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return { graphPath, current: canonical, next: nextFrom(graph, canonical) };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function handleStatus(
|
|
111
|
+
graph: WorkflowGraph | undefined,
|
|
112
|
+
current: string,
|
|
113
|
+
graphPath = "",
|
|
114
|
+
): ActionResult {
|
|
115
|
+
return { graphPath, current, next: nextFrom(graph, current) };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function handleClear(): ActionResult {
|
|
119
|
+
return { graphPath: "", current: "", next: [] };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function formatWidgetText(current: string): string | undefined {
|
|
123
|
+
return current ? `EDA workflow: ${current}` : undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// --- State Reconstruction ---
|
|
127
|
+
|
|
128
|
+
export interface BranchEntry {
|
|
129
|
+
type: string;
|
|
130
|
+
message?: {
|
|
131
|
+
role: string;
|
|
132
|
+
toolName?: string;
|
|
133
|
+
details?: WorkflowTrackerDetails;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function reconstructFromBranch(entries: BranchEntry[]): ActionResult {
|
|
138
|
+
let state: ActionResult = { graphPath: "", current: "", next: [] };
|
|
139
|
+
for (const entry of entries) {
|
|
140
|
+
if (entry.type !== "message") continue;
|
|
141
|
+
const msg = entry.message;
|
|
142
|
+
if (
|
|
143
|
+
!msg ||
|
|
144
|
+
msg.role !== "toolResult" ||
|
|
145
|
+
msg.toolName !== "eda-workflow-tracker"
|
|
146
|
+
)
|
|
147
|
+
continue;
|
|
148
|
+
const details = msg.details;
|
|
149
|
+
if (details && !details.error) {
|
|
150
|
+
state = {
|
|
151
|
+
graphPath: details.graphPath,
|
|
152
|
+
current: details.current,
|
|
153
|
+
next: details.next,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return state;
|
|
158
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-eda",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Pi tooling for interactive exploratory data analysis.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Shivam V Verma",
|
|
7
|
-
"
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/shivam-v-verma/pi-eda.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"pi-package",
|
|
13
|
+
"pi",
|
|
14
|
+
"eda",
|
|
15
|
+
"exploratory-data-analysis"
|
|
16
|
+
],
|
|
8
17
|
"files": [
|
|
9
18
|
"README.md",
|
|
10
19
|
"agents",
|
|
@@ -12,16 +21,29 @@
|
|
|
12
21
|
"extensions",
|
|
13
22
|
"skills"
|
|
14
23
|
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest"
|
|
27
|
+
},
|
|
15
28
|
"peerDependencies": {
|
|
16
29
|
"@earendil-works/pi-coding-agent": "*",
|
|
17
30
|
"@earendil-works/pi-tui": "*",
|
|
18
31
|
"typebox": "*"
|
|
19
32
|
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"vitest": "^4.1.11"
|
|
35
|
+
},
|
|
20
36
|
"pi": {
|
|
21
|
-
"extensions": [
|
|
22
|
-
|
|
37
|
+
"extensions": [
|
|
38
|
+
"./extensions"
|
|
39
|
+
],
|
|
40
|
+
"skills": [
|
|
41
|
+
"./skills"
|
|
42
|
+
],
|
|
23
43
|
"subagents": {
|
|
24
|
-
"agents": [
|
|
44
|
+
"agents": [
|
|
45
|
+
"./agents"
|
|
46
|
+
]
|
|
25
47
|
}
|
|
26
48
|
}
|
|
27
49
|
}
|
|
@@ -23,9 +23,15 @@ and suggest or build the plots that interrogate them. What a confirmed pattern
|
|
|
23
23
|
*means*, the story it tells, is the user's call: hand back a clear summary of
|
|
24
24
|
what the figure shows, not a conclusion dressed as a finding.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
If using this skill, you **must** first:
|
|
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.
|
|
31
|
+
- Read `workflow.md` for instructions for each stage.
|
|
32
|
+
|
|
33
|
+
**Always call `advance` on the `eda-workflow-tracker` when done with a
|
|
34
|
+
stage before performing the next action or moving on to the next stage.**
|
|
29
35
|
|
|
30
36
|
A good hypothesis is usually a simple, clean one -- reachable through better
|
|
31
37
|
framing of the question, not through aggressively slicing the dataset more ways
|
|
@@ -59,10 +65,12 @@ its own narrower table.
|
|
|
59
65
|
|
|
60
66
|
| Mistake | Fix |
|
|
61
67
|
| --- | --- |
|
|
62
|
-
|
|
|
63
|
-
|
|
|
68
|
+
| Doing any work using this skill (e.g. scoping the analysis, peeking at data, building a script) without having initialized `eda-workflow-tracker` with `init` | `init` as soon as you decide to use this skill. `eda-workflow-tracker` is essential to this skill. |
|
|
69
|
+
| Doing a stage without calling `advance` after it | Treat `advance` as part of finishing the stage, not separate bookkeeping. If you just did something on the graph, the next tool call is `advance`, not the action of the next stage |
|
|
70
|
+
| Running the full dataset before a smoke test | See `workflow.md` (`Build script/plot`) -- always sample/time on a subset first |
|
|
71
|
+
| Auto-advancing to the next question without a framing check | See `workflow.md` (`Propose/ask a question`) -- confirm framing before each new question, not once for the whole analysis |
|
|
64
72
|
| Picking the report/subfolder name yourself instead of asking | Ask before creating it -- one name, shared by the file and its folder |
|
|
65
|
-
| Launching a heavy scale-up run while the `script-checker` is still pending | See `
|
|
73
|
+
| Launching a heavy scale-up run while the `script-checker` is still pending | See `workflow.md` (`Dispatch script-checker`) -- wait for it, or the expensive run finishes before a material issue could surface |
|
|
66
74
|
| Deleting/pruning a failed or paused script | Leave it in place with a status docstring unless the user explicitly asks to remove it |
|
|
67
75
|
| Treating an approval-sounding signal ("looks great", "keep going", "I trust your judgment") as done | Ask whether to move to `/skill:finishing-exploratory-data-analysis` -- don't auto-advance and don't stay silent |
|
|
68
76
|
| Chasing a pattern that only appears after trying many slices/cuts of the data | Multiple-comparisons risk -- go back to framing, not more cuts |
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"start": "Scope the analysis",
|
|
3
|
+
"edges": {
|
|
4
|
+
"Scope the analysis": ["Peek at the data"],
|
|
5
|
+
"Peek at the data": ["Propose/ask a question"],
|
|
6
|
+
"Propose/ask a question": ["Build script/plot"],
|
|
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"],
|
|
10
|
+
"Dispatch script-checker": ["Build script/plot", "Dispatch job-runner"],
|
|
11
|
+
"Run now, this session": ["Summarize the answer"],
|
|
12
|
+
"Dispatch job-runner": ["Summarize the answer"],
|
|
13
|
+
"Summarize the answer": ["Continue analysis or lock in?"],
|
|
14
|
+
"Continue analysis or lock in?": [
|
|
15
|
+
"Propose/ask a question",
|
|
16
|
+
"/skill:finishing-exploratory-data-analysis"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,66 +1,184 @@
|
|
|
1
1
|
# Workflow
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
3
|
+
This file is the legend for the EDA workflow.
|
|
4
|
+
|
|
5
|
+
Use the `eda-workflow-tracker` tool to determine what stage you're on; perform
|
|
6
|
+
exactly that stage of work; and then call `advance` before starting the next
|
|
7
|
+
stage. Call `status` whenever you need to know where you are instead of holding
|
|
8
|
+
it in your head.
|
|
9
|
+
|
|
10
|
+
At the end of every turn, ask yourself if you have accomplished the exit
|
|
11
|
+
condition to `advance` to the next stage.
|
|
12
|
+
|
|
13
|
+
- If yes, `advance` should **always** be your first tool call.
|
|
14
|
+
- If no, keep working on the stage until you have reached the exit condition.
|
|
15
|
+
|
|
16
|
+
## Stages
|
|
17
|
+
|
|
18
|
+
### `Scope the analysis`
|
|
19
|
+
|
|
20
|
+
- Before opening the data, ask in one or two sentences what is broadly in
|
|
21
|
+
scope for this analysis. This is a "what are we even doing here", at a
|
|
22
|
+
high level.
|
|
23
|
+
- Keep interrogating until the user agrees with the framing. Do not advance
|
|
24
|
+
on a partial answer or lukewarm agreement.
|
|
25
|
+
- Ask the user for a report name for this analysis -- it names both the
|
|
26
|
+
report file and its paired folder (see `directory-structure.md`).
|
|
27
|
+
Don't pick one yourself. Keep asking until they give one.
|
|
28
|
+
- Expect the scope to narrow or shift as the data yields a story during
|
|
29
|
+
this analysis. This step is purely for you to ground yourself.
|
|
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`.
|
|
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.
|
|
35
|
+
|
|
36
|
+
### `Peek at the data`
|
|
37
|
+
|
|
38
|
+
- Load the dataset (lazily), check schema/dtypes.
|
|
39
|
+
- Get null counts and basic summary stats (mean/median, min/max, std).
|
|
40
|
+
- Get a feel for the actual data: `.head()`, a strided sample, or a random
|
|
41
|
+
sample (fixed seed).
|
|
42
|
+
- Read a few raw rows by eye. They catch what schema/dtype checks and basic
|
|
43
|
+
stats above miss: a "numeric" column that's really a formatted string,
|
|
44
|
+
an ID convention, units, or what a free-text field actually holds.
|
|
45
|
+
- Even raw rows and basic stats can miss duplicate rows.
|
|
46
|
+
- Beyond that, think about what else is cheap to calculate and might
|
|
47
|
+
reveal something missed above -- e.g. sentinel/placeholder values (`-1`,
|
|
48
|
+
`0`, `"unknown"`), class imbalance, or a grain mismatch after a join.
|
|
49
|
+
- `advance` condition: you understand the high-level structure of the data.
|
|
50
|
+
|
|
51
|
+
### `Propose/ask a question`
|
|
52
|
+
|
|
53
|
+
- Propose a framing for *this* question/figure yourself, then keep
|
|
54
|
+
interrogating until the user agrees with it, before writing a script for it.
|
|
55
|
+
- You can refine the approach by writing/executing some code, but don't write
|
|
56
|
+
any scripts in the directory yet. This is exploratory, not polish.
|
|
57
|
+
- The goal here is focus: work with the user to identify a specific, question
|
|
58
|
+
that can be tested with data. Do not attempt many questions or approaches at
|
|
59
|
+
once. Iterate one at a time with the user until it's settled.
|
|
60
|
+
- If the user has already proposed a specific question, confirm your
|
|
61
|
+
understanding vocally and then `advance`.
|
|
62
|
+
- `advance` condition: You have explained what you plan to do, and the user
|
|
63
|
+
agrees with the question and approach.
|
|
64
|
+
|
|
65
|
+
### `Build script/plot`
|
|
66
|
+
|
|
67
|
+
Goal: Write the script/plot (see `plotting.md` for chart conventions), then
|
|
68
|
+
build confidence in it:
|
|
69
|
+
|
|
70
|
+
- After each nontrivial transform (groupby, join, pivot, filter, etc.), do
|
|
71
|
+
a quick check (`.head()`, row counts, spot-check a few values) that the
|
|
72
|
+
output matches what the transform should have done. Don't just do this once
|
|
73
|
+
at the end of the script.
|
|
74
|
+
- Pick a case that could actually fail, not the first one at hand.
|
|
75
|
+
especially for canonicalization/ordering/matching logic, where an easy
|
|
76
|
+
case can pass while hiding a real bug.
|
|
77
|
+
- Time the script on a small subset of the data to understand how long the
|
|
78
|
+
full scale run will take
|
|
79
|
+
- Glance at the actual output values to sanity check the output.
|
|
80
|
+
- `advance` condition: the script is written and each nontrivial transform has
|
|
81
|
+
been spot-checked.
|
|
82
|
+
|
|
83
|
+
### `Light or heavy?`
|
|
84
|
+
|
|
85
|
+
Goal: Decide whether the full scale run will be a light task or a heavy task.
|
|
86
|
+
We want to increase iteration speed and not waste user time.
|
|
87
|
+
|
|
88
|
+
- Judge from the smoke-test timing/size ratio whether the full-scale run
|
|
89
|
+
will be heavy (long-running or memory-heavy relative to the sample --
|
|
90
|
+
e.g. 1% took a second but full scale extrapolates to minutes).
|
|
91
|
+
- Rough thresholds based on estimated time: light if under ~1 minute,
|
|
92
|
+
heavy if over ~5 minutes. In between is a judgment call.
|
|
93
|
+
- State that judgment explicitly: "sample took Xs, full run estimated ~Y,
|
|
94
|
+
treating as heavy."
|
|
95
|
+
- `advance` condition: you understand the computational cost of the script.
|
|
96
|
+
|
|
97
|
+
### `Reread script`
|
|
98
|
+
|
|
99
|
+
Light path: Reread the script as a sanity check.
|
|
100
|
+
|
|
101
|
+
- Does it match the user's stated intent/framing, does each nontrivial
|
|
102
|
+
transform do what it should?
|
|
103
|
+
- If the reread surfaces an issue, fix it and reread again before
|
|
104
|
+
running. Don't run first and raise the concern after.
|
|
105
|
+
- This is a deliberately lower bar than the heavy path. A subtle mistake here
|
|
106
|
+
can also be caught downstream in `/skill:finishing-exploratory-data-analysis`.
|
|
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.
|
|
115
|
+
|
|
116
|
+
### `Dispatch script-checker`
|
|
117
|
+
|
|
118
|
+
Heavy path: dispatch the `script-checker` and wait, before launching.
|
|
119
|
+
|
|
120
|
+
- `subagent({agent:"script-checker",
|
|
121
|
+
task:"<script path + stated intent/framing>"})`.
|
|
122
|
+
- This must be a blocking call. wait for the `script-checker` to finish. You
|
|
123
|
+
don't want to surface issues after the expensive run has finished. We want to
|
|
124
|
+
catch all issues here.
|
|
125
|
+
- Two or more edits since the last `script-checker` dispatch count as one
|
|
126
|
+
nontrivial change on their own. Dispatch again at that point; don't wait for
|
|
127
|
+
it to feel big enough.
|
|
128
|
+
- "Issues found" loops back to `Build script/plot`, "clean" goes to `Dispatch
|
|
129
|
+
job-runner`.
|
|
130
|
+
- `advance` condition: `script-checker` has not found substantial issues, and
|
|
131
|
+
all issues have been handled and rechecked.
|
|
132
|
+
|
|
133
|
+
### `Dispatch job-runner`
|
|
134
|
+
|
|
135
|
+
Heavy path continuation: launch the `job-runner` only once the
|
|
136
|
+
`script-checker` is clean.
|
|
137
|
+
|
|
138
|
+
- Run the `job runner` with `subagent({agent:"job-runner", task:"<script path +
|
|
139
|
+
output location + estimated runtime>"})`.
|
|
140
|
+
- Pass the runtime estimate to the `job-runner` from your smoke test in
|
|
141
|
+
`Light or heavy?` above rather than making it re-derive it blind.
|
|
142
|
+
- Register a wake instead of blocking: `subagent_wait({id, nonBlocking: true})`
|
|
143
|
+
after launching. Non-blocking waits allow you to continue the conversation
|
|
144
|
+
with the user while waiting.
|
|
145
|
+
- `advance` condition: `job-runner` has successfully completed the job.
|
|
146
|
+
|
|
147
|
+
### `Summarize the answer`
|
|
148
|
+
|
|
149
|
+
- `show` the figure(s) to the user, then summarize briefly with bullets.
|
|
150
|
+
- Core principle: the user decides the interpretation. Continue interrogating
|
|
151
|
+
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.
|
|
160
|
+
|
|
161
|
+
### `/skill:finishing-exploratory-data-analysis`
|
|
162
|
+
|
|
163
|
+
Figure/analysis seems meaningful, ready to write up or commit.
|
|
60
164
|
|
|
61
165
|
## Common Mistakes
|
|
62
166
|
|
|
63
167
|
| Mistake | Fix |
|
|
64
168
|
| --- | --- |
|
|
65
|
-
|
|
|
66
|
-
|
|
|
169
|
+
| Getting the user's agreement on a confirmation stage, then acting on it without calling `advance` first | The agreement itself completes the stage -- `advance` is the first tool call once you have it, not a follow-up after next-stage work has already started |
|
|
170
|
+
| Treating any issue in scripts (via self-reread or `script-checker`) as a note to fold in later instead of a loop back to the script | Go to `Build script/plot` and fix it -- see the graph's "issues found" edges |
|
|
171
|
+
| Opening the data before agreeing on a broad scope | State the analysis's scope/question area in a sentence and confirm before `Peek at the data` |
|
|
172
|
+
| Picking the report/subfolder name yourself instead of asking | Ask before creating it -- one name, shared by the file and its folder |
|
|
173
|
+
| Treating EDA as a pipeline and auto-advancing through questions without a framing check | Confirm framing per question, not once for the whole analysis |
|
|
174
|
+
| Adding a follow-up figure/question under an earlier blanket go-ahead without confirming its framing | State the new framing in a sentence before scripting it |
|
|
175
|
+
| Building the next two questions back-to-back and presenting a "complete report" because the user said "keep going" / "I trust your judgment" | That phrase covers continued work, not skipped per-question confirmation -- stop after each one |
|
|
176
|
+
| Skipping the raw-row eyeball and stopping at summary stats | Read a few actual rows -- aggregates hide format/unit/encoding surprises |
|
|
177
|
+
| Running the full dataset (or an ad hoc exploratory probe) before a smoke test | Always sample/time on a subset first -- applies to one-off probes too, not just numbered scripts |
|
|
178
|
+
| 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
|
+
| 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
|
+
| 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 |
|
|
182
|
+
| 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
|
+
| 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
|
+
| 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 |
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
# Checking and Running Scripts
|
|
2
|
-
|
|
3
|
-
Building confidence in a script for `/skill:exploring-data`, then deciding
|
|
4
|
-
how to run it. See `workflow.md` for where this fits in the full loop
|
|
5
|
-
(the light/heavy fork is inlined there, each branch with its own check
|
|
6
|
-
step and loop-back on findings).
|
|
7
|
-
|
|
8
|
-
## Sanity Checking a Script
|
|
9
|
-
|
|
10
|
-
- After each nontrivial transform (groupby, join, pivot, filter, etc.), do
|
|
11
|
-
a quick check (`.head()`, row counts, spot-check a few values) that the
|
|
12
|
-
output matches what the transform should have done -- not just once at
|
|
13
|
-
the end of the script.
|
|
14
|
-
- Pick a case that could actually fail, not the first one at hand --
|
|
15
|
-
especially for canonicalization/ordering/matching logic, where an easy
|
|
16
|
-
case can pass while hiding a real bug.
|
|
17
|
-
|
|
18
|
-
## Smoke Testing a Script
|
|
19
|
-
|
|
20
|
-
- Run on a sampled/head subset (fixed seed) and time it before running at
|
|
21
|
-
full scale.
|
|
22
|
-
- After it runs, glance at the actual output values, not just "it
|
|
23
|
-
completed in time." A fast clean run on garbage output is not a
|
|
24
|
-
passing smoke test.
|
|
25
|
-
- Exception: a cached-read-and-plot script -- one that purely reads an
|
|
26
|
-
already-validated cached table and plots it, with zero computation (no
|
|
27
|
-
groupby, join, pivot, or filter). State that justification explicitly
|
|
28
|
-
instead of skipping silently.
|
|
29
|
-
|
|
30
|
-
## Judging Heavy vs. Light Scale-up
|
|
31
|
-
|
|
32
|
-
Goal: increase iteration speed without being reckless. Either extreme
|
|
33
|
-
wastes the user's time -- blocking every quick diff on a check likely to
|
|
34
|
-
pass, or committing to a large/expensive run that turns out wrong.
|
|
35
|
-
|
|
36
|
-
- Judge from the smoke-test timing/size ratio whether the full-scale run
|
|
37
|
-
will be heavy (long-running or memory-heavy relative to the sample --
|
|
38
|
-
e.g. 1% took a second but full scale extrapolates to minutes).
|
|
39
|
-
- Rough thresholds based on estimated time: light if under ~1 minute,
|
|
40
|
-
heavy if over ~5 minutes. In between is a judgment call.
|
|
41
|
-
- State that judgment explicitly: "sample took Xs, full run estimated ~Y,
|
|
42
|
-
treating as heavy."
|
|
43
|
-
|
|
44
|
-
## Light Scale-up
|
|
45
|
-
|
|
46
|
-
- No `script-checker` dispatch -- reread the script yourself before
|
|
47
|
-
running: does it match the user's stated intent/framing, does each
|
|
48
|
-
nontrivial transform do what it should.
|
|
49
|
-
- If the reread surfaces an issue, fix it and reread again before
|
|
50
|
-
running -- don't run first and raise the concern after.
|
|
51
|
-
- This is a deliberately lower bar than the heavy path: a light-path
|
|
52
|
-
mistake is cheap to catch downstream too, since the run finishes fast
|
|
53
|
-
and you're looking at the output right away. The real second-set-of-
|
|
54
|
-
eyes gate for the analysis still happens at
|
|
55
|
-
`/skill:finishing-exploratory-data-analysis`'s lock-in checkpoint --
|
|
56
|
-
this reread isn't the last line of defense, it's a cheap intermediate
|
|
57
|
-
one.
|
|
58
|
-
|
|
59
|
-
## Heavy Scale-up
|
|
60
|
-
|
|
61
|
-
- Dispatch the `script-checker` before launching:
|
|
62
|
-
`subagent({agent:"script-checker", task:"<script path + stated intent/framing>"})`.
|
|
63
|
-
- Do not launch until it has returned and come back clean.
|
|
64
|
-
- If it's still running, wait for it (or say explicitly you're waiting)
|
|
65
|
-
-- don't launch anyway planning to fold in feedback once it lands.
|
|
66
|
-
- Launching "in parallel while the `script-checker` runs in the
|
|
67
|
-
background" still counts as launching without it -- the expensive run
|
|
68
|
-
finishes and gets shown to the user before a material issue could
|
|
69
|
-
surface.
|
|
70
|
-
- A `script-checker` pass only checks the script -- it never substitutes
|
|
71
|
-
for the lock-in checkpoint in
|
|
72
|
-
`/skill:finishing-exploratory-data-analysis` once a figure/question is
|
|
73
|
-
ready to show the user.
|
|
74
|
-
- Two or more edits since the last `script-checker` dispatch count as one
|
|
75
|
-
nontrivial change on their own -- dispatch again at that point, don't
|
|
76
|
-
wait for it to feel big enough.
|
|
77
|
-
|
|
78
|
-
### Dispatching the `job-runner`
|
|
79
|
-
|
|
80
|
-
- Once clear to launch:
|
|
81
|
-
`subagent({agent:"job-runner", task:"<script path + output location + estimated runtime + estimated peak memory>"})`.
|
|
82
|
-
This frees the main session to keep iterating while it runs. Pass the
|
|
83
|
-
runtime/memory estimate from your smoke test (see "Judging Heavy vs.
|
|
84
|
-
Light Scale-up" above) rather than making it re-derive them blind.
|
|
85
|
-
|
|
86
|
-
## Common Mistakes
|
|
87
|
-
|
|
88
|
-
| Mistake | Fix |
|
|
89
|
-
| --- | --- |
|
|
90
|
-
| Running the full dataset (or an ad hoc exploratory probe) before a smoke test | Always sample/time on a subset first -- applies to one-off probes too, not just numbered scripts |
|
|
91
|
-
| 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 |
|
|
92
|
-
| 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 |
|
|
93
|
-
| 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 |
|
|
94
|
-
| 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 |
|
|
95
|
-
| 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 |
|
|
96
|
-
| 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 |
|
|
97
|
-
| 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 |
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Pre-Code Checkpoints
|
|
2
|
-
|
|
3
|
-
What to do for `/skill:exploring-data` before you write any
|
|
4
|
-
code for the next question.
|
|
5
|
-
|
|
6
|
-
## Scope the analysis (once per analysis, before Peek at the data)
|
|
7
|
-
|
|
8
|
-
- Before opening the data, check in one or two sentences what is broadly in
|
|
9
|
-
scope for this analysis and get the user's confirmation. This is a "what
|
|
10
|
-
are we even doing here", separate from and prior to the per-question framing
|
|
11
|
-
below.
|
|
12
|
-
- Ask the user for a report name for this analysis -- it names both the
|
|
13
|
-
report file and its paired folder (see `directory-structure.md`).
|
|
14
|
-
Don't pick one yourself.
|
|
15
|
-
- Keep it broad on purpose -- treat it as provisional, not a lock. Expect
|
|
16
|
-
it to narrow or shift as the data reveals what's actually there, well
|
|
17
|
-
before you reach conclusions -- confirm the new framing with the user
|
|
18
|
-
when it does, same as any other question (see below). `index.md` and
|
|
19
|
-
the report are written together during
|
|
20
|
-
`/skill:finishing-exploratory-data-analysis`, not during exploration.
|
|
21
|
-
|
|
22
|
-
## Peek at the data (once per new dataset, before Propose/ask a question)
|
|
23
|
-
|
|
24
|
-
- Load the dataset (lazily), check schema/dtypes.
|
|
25
|
-
- Get null counts and basic summary stats (mean/median, min/max, std).
|
|
26
|
-
- Get a feel for the actual data: `.head()`, a strided sample, or a random
|
|
27
|
-
sample (fixed seed).
|
|
28
|
-
- Read a few raw rows by eye. They catch what schema/dtype checks and basic
|
|
29
|
-
stats above miss: a "numeric" column that's really a formatted string,
|
|
30
|
-
an ID convention, units, or what a free-text field actually holds.
|
|
31
|
-
- Even raw rows and basic stats can miss duplicate rows.
|
|
32
|
-
- Beyond that, think about what else is cheap to calculate and might
|
|
33
|
-
reveal something missed above -- e.g. sentinel/placeholder values (`-1`,
|
|
34
|
-
`0`, `"unknown"`), class imbalance, or a grain mismatch after a join.
|
|
35
|
-
|
|
36
|
-
## Propose/ask a question (once per question, every time)
|
|
37
|
-
|
|
38
|
-
- Confirm the framing for *this* question/figure with the user before
|
|
39
|
-
writing a script for it.
|
|
40
|
-
- State the new framing in a sentence before scripting it, even if a broad
|
|
41
|
-
go-ahead was already given for the analysis as a whole.
|
|
42
|
-
- This applies to every question, including one you decide to add on your
|
|
43
|
-
own after an earlier go-ahead. Approval for one question is not
|
|
44
|
-
approval for the next one the user hasn't seen proposed yet.
|
|
45
|
-
- **A blanket "keep going" / "I trust your judgment" is not per-question
|
|
46
|
-
approval.** It authorizes you to keep working on framing, not to skip
|
|
47
|
-
asking about the next question's framing. Treat it the same as
|
|
48
|
-
silence: propose the next question's framing in one sentence, then
|
|
49
|
-
build it.
|
|
50
|
-
- Do not build multiple questions in one pass and hand over a "finished
|
|
51
|
-
report" -- stop after each one and surface what it shows before
|
|
52
|
-
moving to the next.
|
|
53
|
-
|
|
54
|
-
## Common Mistakes
|
|
55
|
-
|
|
56
|
-
| Mistake | Fix |
|
|
57
|
-
| --- | --- |
|
|
58
|
-
| Opening the data before agreeing on a broad scope | State the analysis's scope/question area in a sentence and confirm before Peek at the data |
|
|
59
|
-
| Picking the report/subfolder name yourself instead of asking | Ask before creating it -- one name, shared by the file and its folder |
|
|
60
|
-
| Treating EDA as a pipeline and auto-advancing through questions without a framing check | Confirm framing per question, not once for the whole analysis |
|
|
61
|
-
| Adding a follow-up figure/question under an earlier blanket go-ahead without confirming its framing | State the new framing in a sentence before scripting it |
|
|
62
|
-
| Building the next two questions back-to-back and presenting a "complete report" because the user said "keep going" / "I trust your judgment" | That phrase covers continued work, not skipped per-question confirmation -- stop after each one |
|
|
63
|
-
| Skipping the raw-row eyeball and stopping at summary stats | Read a few actual rows -- aggregates hide format/unit/encoding surprises |
|