pi-eda 0.1.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 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
 
@@ -12,10 +13,16 @@ The [`pi-subagents`](https://www.npmjs.com/package/pi-subagents) package, for
12
13
  custom-agent support and the `/skill:pi-subagents` dispatch pattern the skills
13
14
  reference.
14
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
+
15
20
  ## Install
16
21
 
22
+ Install dependencies & this package:
23
+
17
24
  ```bash
18
- pi install git:github.com/shivam-v-verma/pi-eda
25
+ pi install npm:pi-subagents
19
26
  pi install npm:pi-eda
20
27
  ```
21
28
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: job-runner
3
- description: Launches and monitors a heavy/long-running analysis script in the background
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 and peak memory from the smoke test (should be
16
- in the task you were given -- if missing, ask the dispatching agent
17
- for it rather than guessing).
18
- - Whether `systemd-run` is available on this machine.
19
- - Launch the script as a background process (following the project's run
20
- convention, e.g. `uv run`).
21
- - Cap it with a memory limit so a runaway process gets killed by the OS
22
- instead of taking down the machine, sized off the estimate with
23
- headroom (e.g. 2x the estimated peak):
24
- - `systemd-run --scope -p MemoryMax=<budget> --user -- uv run <script>`
25
- if available.
26
- - Otherwise, say so explicitly and fall back to periodic `ps`/`smem`
27
- checks with a manual kill.
28
- - Monitor progress and memory use periodically rather than blocking on it
29
- synchronously -- this is in addition to the hard cap above, not a
30
- replacement for it, since you want to know about a job trending toward
31
- the ceiling before it gets killed.
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,267 @@
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 { fileURLToPath } from "node:url";
22
+ import type {
23
+ ExtensionAPI,
24
+ ExtensionContext,
25
+ } from "@earendil-works/pi-coding-agent";
26
+ import { Text } from "@earendil-works/pi-tui";
27
+ import { Type, type Static } from "typebox";
28
+ import {
29
+ type WorkflowGraph,
30
+ type WorkflowTrackerDetails,
31
+ type BranchEntry,
32
+ type ActionResult,
33
+ isValidGraph,
34
+ handleInit,
35
+ handleAdvance,
36
+ handleStatus,
37
+ handleClear,
38
+ formatWidgetText,
39
+ resolveGraphPath,
40
+ reconstructFromBranch,
41
+ } from "./lib/eda-workflow-tracker-core.js";
42
+
43
+ const bundledGraphPath = fileURLToPath(
44
+ new URL("../skills/exploring-data/workflow.graph.json", import.meta.url),
45
+ );
46
+
47
+ const WorkflowTrackerParams = Type.Object({
48
+ action: Type.Union(
49
+ [
50
+ Type.Literal("init"),
51
+ Type.Literal("advance"),
52
+ Type.Literal("status"),
53
+ Type.Literal("clear"),
54
+ ],
55
+ { description: "Action to perform" },
56
+ ),
57
+ graphPath: Type.Optional(
58
+ Type.String({
59
+ description:
60
+ "Path to a workflow.graph.json ({ start, edges }); defaults to the bundled EDA graph (for init)",
61
+ }),
62
+ ),
63
+ to: Type.Optional(
64
+ Type.String({
65
+ description: "Node name to advance to (for advance)",
66
+ }),
67
+ ),
68
+ force: Type.Optional(
69
+ Type.Boolean({
70
+ description:
71
+ "Allow advancing to any valid node in the graph, not just ones adjacent to the current node (for advance)",
72
+ }),
73
+ ),
74
+ });
75
+
76
+ export type WorkflowTrackerInput = Static<typeof WorkflowTrackerParams>;
77
+
78
+ export default function (pi: ExtensionAPI) {
79
+ let graphPath = "";
80
+ let graph: WorkflowGraph | undefined;
81
+ let current = "";
82
+
83
+ const loadGraphFile = async (cwd: string, path: string): Promise<unknown> => {
84
+ const raw = await readFile(resolve(cwd, path), "utf-8");
85
+ return JSON.parse(raw);
86
+ };
87
+
88
+ const reconstructState = async (ctx: ExtensionContext) => {
89
+ const state = reconstructFromBranch(
90
+ ctx.sessionManager.getBranch() as BranchEntry[],
91
+ );
92
+ graphPath = state.graphPath;
93
+ current = state.current;
94
+ graph = undefined;
95
+ if (!graphPath) return;
96
+ try {
97
+ const parsed = await loadGraphFile(ctx.cwd, graphPath);
98
+ if (isValidGraph(parsed)) graph = parsed;
99
+ } catch {
100
+ // leave graph undefined -- advance/status report it as not loaded
101
+ }
102
+ };
103
+
104
+ const updateWidget = (ctx: ExtensionContext) => {
105
+ if (!ctx.hasUI) return;
106
+ const text = formatWidgetText(current);
107
+ if (text) {
108
+ ctx.ui.setWidget("eda-workflow-tracker", (_tui, theme) => {
109
+ return new Text(theme.fg("muted", text), 0, 0);
110
+ });
111
+ } else {
112
+ ctx.ui.setWidget("eda-workflow-tracker", undefined);
113
+ }
114
+ };
115
+
116
+ for (const event of [
117
+ "session_start",
118
+ "session_switch",
119
+ "session_fork",
120
+ "session_tree",
121
+ ] as const) {
122
+ pi.on(event, async (_event, ctx) => {
123
+ await reconstructState(ctx);
124
+ updateWidget(ctx);
125
+ });
126
+ }
127
+
128
+ pi.registerTool({
129
+ name: "eda-workflow-tracker",
130
+ label: "Workflow Tracker",
131
+ description:
132
+ "Track the current state in a named-state workflow graph (branches/loop-backs, not a flat " +
133
+ "todo list). Actions: init (load a workflow.graph.json and set current to its start), " +
134
+ "advance (move to a node; must be adjacent to current unless force is set, in which case " +
135
+ "any valid node in the graph is allowed), status (show current state), clear (reset).",
136
+ parameters: WorkflowTrackerParams,
137
+
138
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
139
+ let result: ActionResult;
140
+
141
+ switch (params.action) {
142
+ case "init": {
143
+ const selectedGraphPath = resolveGraphPath(
144
+ params.graphPath,
145
+ bundledGraphPath,
146
+ );
147
+ try {
148
+ const parsed = await loadGraphFile(ctx.cwd, selectedGraphPath);
149
+ if (!isValidGraph(parsed)) {
150
+ throw new Error("expected { start: string, edges: object }");
151
+ }
152
+ graph = parsed;
153
+ } catch (err) {
154
+ result = {
155
+ graphPath,
156
+ current,
157
+ next: [],
158
+ error: `failed to load graph at "${selectedGraphPath}": ${(err as Error).message}`,
159
+ };
160
+ break;
161
+ }
162
+ result = handleInit(graph, selectedGraphPath);
163
+ graphPath = result.graphPath;
164
+ current = result.current;
165
+ updateWidget(ctx);
166
+ break;
167
+ }
168
+ case "advance": {
169
+ if (!graph) {
170
+ result = {
171
+ graphPath,
172
+ current,
173
+ next: [],
174
+ error: "no graph loaded -- call init first",
175
+ };
176
+ break;
177
+ }
178
+ if (!params.to) {
179
+ result = {
180
+ graphPath,
181
+ current,
182
+ next: [],
183
+ error: "to required for advance",
184
+ };
185
+ break;
186
+ }
187
+ result = handleAdvance(
188
+ graph,
189
+ current,
190
+ params.to,
191
+ params.force ?? false,
192
+ graphPath,
193
+ );
194
+ current = result.current;
195
+ updateWidget(ctx);
196
+ break;
197
+ }
198
+ case "status": {
199
+ result = handleStatus(graph, current, graphPath);
200
+ break;
201
+ }
202
+ case "clear": {
203
+ result = handleClear();
204
+ graphPath = result.graphPath;
205
+ current = result.current;
206
+ graph = undefined;
207
+ updateWidget(ctx);
208
+ break;
209
+ }
210
+ default: {
211
+ result = {
212
+ graphPath,
213
+ current,
214
+ next: [],
215
+ error: `unknown action: ${params.action as string}`,
216
+ };
217
+ }
218
+ }
219
+
220
+ const details: WorkflowTrackerDetails = {
221
+ action: params.action,
222
+ graphPath: result.graphPath,
223
+ current: result.current,
224
+ next: result.next,
225
+ ...(result.error ? { error: result.error } : {}),
226
+ };
227
+
228
+ return {
229
+ content: [
230
+ {
231
+ type: "text",
232
+ text: result.error
233
+ ? `Error: ${result.error}`
234
+ : `current: "${result.current}"; next: ${JSON.stringify(result.next)}`,
235
+ },
236
+ ],
237
+ details,
238
+ };
239
+ },
240
+
241
+ renderCall(args, theme) {
242
+ let text = theme.fg("toolTitle", theme.bold("eda-workflow-tracker "));
243
+ text += theme.fg("muted", args.action);
244
+ if (args.action === "advance" && args.to) {
245
+ text += ` -> ${theme.fg("accent", args.to)}`;
246
+ if (args.force) text += theme.fg("dim", " (force)");
247
+ }
248
+ return new Text(text, 0, 0);
249
+ },
250
+
251
+ renderResult(result, _options, theme) {
252
+ const details = result.details as WorkflowTrackerDetails | undefined;
253
+ if (!details) {
254
+ const text = result.content[0];
255
+ return new Text(text?.type === "text" ? text.text : "", 0, 0);
256
+ }
257
+ if (details.error) {
258
+ return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
259
+ }
260
+ return new Text(
261
+ theme.fg("success", "✓ ") + theme.fg("muted", details.current),
262
+ 0,
263
+ 0,
264
+ );
265
+ },
266
+ });
267
+ }
@@ -0,0 +1,181 @@
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 a known start node and string-array edges. */
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
+ 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;
46
+ }
47
+
48
+ export interface WorkflowTrackerDetails {
49
+ action: "init" | "advance" | "status" | "clear";
50
+ graphPath: string;
51
+ current: string;
52
+ next: string[];
53
+ error?: string;
54
+ }
55
+
56
+ export interface ActionResult {
57
+ graphPath: string;
58
+ current: string;
59
+ next: string[];
60
+ error?: string;
61
+ }
62
+
63
+ /** Nodes reachable in one step from `node`, or [] if none/no graph loaded. */
64
+ function nextFrom(graph: WorkflowGraph | undefined, node: string): string[] {
65
+ return graph?.edges[node] ?? [];
66
+ }
67
+
68
+ function allNodes(edges: Record<string, string[]>): Set<string> {
69
+ const nodes = new Set<string>(Object.keys(edges));
70
+ for (const targets of Object.values(edges)) {
71
+ for (const target of targets) nodes.add(target);
72
+ }
73
+ return nodes;
74
+ }
75
+
76
+ /** Case-insensitive lookup: returns the canonically-cased node name, or undefined if none matches. */
77
+ function canonicalize(
78
+ candidates: Iterable<string>,
79
+ name: string,
80
+ ): string | undefined {
81
+ const lower = name.toLowerCase();
82
+ for (const candidate of candidates) {
83
+ if (candidate.toLowerCase() === lower) return candidate;
84
+ }
85
+ return undefined;
86
+ }
87
+
88
+ export function handleInit(
89
+ graph: WorkflowGraph,
90
+ graphPath: string,
91
+ ): ActionResult {
92
+ return {
93
+ graphPath,
94
+ current: graph.start,
95
+ next: nextFrom(graph, graph.start),
96
+ };
97
+ }
98
+
99
+ export function handleAdvance(
100
+ graph: WorkflowGraph,
101
+ current: string,
102
+ to: string,
103
+ force: boolean,
104
+ graphPath = "",
105
+ ): ActionResult {
106
+ const legalFromCurrent = graph.edges[current] ?? [];
107
+
108
+ if (!force) {
109
+ const canonical = canonicalize(legalFromCurrent, to);
110
+ if (!canonical) {
111
+ return {
112
+ graphPath,
113
+ current,
114
+ next: legalFromCurrent,
115
+ error: `"${to}" is not reachable from "${current}"; legal: ${JSON.stringify(legalFromCurrent)}`,
116
+ };
117
+ }
118
+ return { graphPath, current: canonical, next: nextFrom(graph, canonical) };
119
+ }
120
+
121
+ const canonical = canonicalize(allNodes(graph.edges), to);
122
+ if (!canonical) {
123
+ return {
124
+ graphPath,
125
+ current,
126
+ next: legalFromCurrent,
127
+ error: `"${to}" is not a known node in this graph; legal: ${JSON.stringify(legalFromCurrent)}`,
128
+ };
129
+ }
130
+ return { graphPath, current: canonical, next: nextFrom(graph, canonical) };
131
+ }
132
+
133
+ export function handleStatus(
134
+ graph: WorkflowGraph | undefined,
135
+ current: string,
136
+ graphPath = "",
137
+ ): ActionResult {
138
+ return { graphPath, current, next: nextFrom(graph, current) };
139
+ }
140
+
141
+ export function handleClear(): ActionResult {
142
+ return { graphPath: "", current: "", next: [] };
143
+ }
144
+
145
+ export function formatWidgetText(current: string): string | undefined {
146
+ return current ? `EDA workflow: ${current}` : undefined;
147
+ }
148
+
149
+ // --- State Reconstruction ---
150
+
151
+ export interface BranchEntry {
152
+ type: string;
153
+ message?: {
154
+ role: string;
155
+ toolName?: string;
156
+ details?: WorkflowTrackerDetails;
157
+ };
158
+ }
159
+
160
+ export function reconstructFromBranch(entries: BranchEntry[]): ActionResult {
161
+ let state: ActionResult = { graphPath: "", current: "", next: [] };
162
+ for (const entry of entries) {
163
+ if (entry.type !== "message") continue;
164
+ const msg = entry.message;
165
+ if (
166
+ !msg ||
167
+ msg.role !== "toolResult" ||
168
+ msg.toolName !== "eda-workflow-tracker"
169
+ )
170
+ continue;
171
+ const details = msg.details;
172
+ if (details && !details.error) {
173
+ state = {
174
+ graphPath: details.graphPath,
175
+ current: details.current,
176
+ next: details.next,
177
+ };
178
+ }
179
+ }
180
+ return state;
181
+ }
@@ -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: [
package/package.json CHANGED
@@ -1,10 +1,19 @@
1
1
  {
2
2
  "name": "pi-eda",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Pi tooling for interactive exploratory data analysis.",
5
5
  "license": "MIT",
6
6
  "author": "Shivam V Verma",
7
- "keywords": ["pi-package", "pi", "eda", "exploratory-data-analysis"],
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": ["./extensions"],
22
- "skills": ["./skills"],
37
+ "extensions": [
38
+ "./extensions"
39
+ ],
40
+ "skills": [
41
+ "./skills"
42
+ ],
23
43
  "subagents": {
24
- "agents": ["./agents"]
44
+ "agents": [
45
+ "./agents"
46
+ ]
25
47
  }
26
48
  }
27
49
  }
@@ -23,9 +23,14 @@ 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
- See `workflow.md` for stages of this skill. In all stages, confirm framing
27
- with the user. Never run a batch of steps by yourself; always interleave
28
- by checking for the user's insight/thoughts.
26
+ If using this skill, you **must** first:
27
+
28
+ - Run `init` on `eda-workflow-tracker` for guidance on what stage you should be
29
+ working on.
30
+ - Read `workflow.md` for instructions for each stage.
31
+
32
+ **Always call `advance` on the `eda-workflow-tracker` when done with a
33
+ stage before performing the next action or moving on to the next stage.**
29
34
 
30
35
  A good hypothesis is usually a simple, clean one -- reachable through better
31
36
  framing of the question, not through aggressively slicing the dataset more ways
@@ -59,10 +64,12 @@ its own narrower table.
59
64
 
60
65
  | Mistake | Fix |
61
66
  | --- | --- |
62
- | Running the full dataset before a smoke test | See `checking-and-running-scripts.md` -- always sample/time on a subset first |
63
- | Auto-advancing to the next question without a framing check | See `pre-code-checkpoints.md` -- confirm framing before each new question, not once for the whole analysis |
67
+ | 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. |
68
+ | 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 |
69
+ | Running the full dataset before a smoke test | See `workflow.md` (`Build script/plot`) -- always sample/time on a subset first |
70
+ | 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
71
  | 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 `checking-and-running-scripts.md` -- wait for it, or the expensive run finishes before a material issue could surface |
72
+ | 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
73
  | Deleting/pruning a failed or paused script | Leave it in place with a status docstring unless the user explicitly asks to remove it |
67
74
  | 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
75
  | Chasing a pattern that only appears after trying many slices/cuts of the data | Multiple-comparisons risk -- go back to framing, not more cuts |
@@ -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
@@ -0,0 +1,18 @@
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 and run script", "Dispatch script-checker"],
9
+ "Reread and run script": ["Build script/plot", "Summarize the answer"],
10
+ "Dispatch script-checker": ["Build script/plot", "Dispatch job-runner"],
11
+ "Dispatch job-runner": ["Summarize the answer"],
12
+ "Summarize the answer": ["Continue analysis or lock in?"],
13
+ "Continue analysis or lock in?": [
14
+ "Propose/ask a question",
15
+ "/skill:finishing-exploratory-data-analysis"
16
+ ]
17
+ }
18
+ }
@@ -1,66 +1,181 @@
1
1
  # Workflow
2
2
 
3
- Loop/flow-control for `/skill:exploring-data`.
4
-
5
- The user drives this loop: they propose or pick the next thing to plot or
6
- check, you build it and return what it shows. They then interpret it
7
- and pick next steps or ask additional questions.
8
-
9
- This is a looped pipeline, exiting only when the user decides to
10
- lock-in an analysis.
11
-
12
- ```dot
13
- digraph EDA_WORKFLOW {
14
- "Start analysis" [shape=doublecircle];
15
- "Scope the analysis" [shape=box, style=filled, fillcolor=lightblue];
16
- "Peek at the data" [shape=box, style=filled, fillcolor=lightyellow];
17
- "Propose/ask a question" [shape=box, style=filled, fillcolor=lightblue];
18
- "Build script/plot" [shape=box];
19
- "light or heavy?" [shape=diamond];
20
- "Reread script" [shape=box];
21
- "Dispatch script-checker (blocking)" [shape=box];
22
- "run now, this session" [shape=box];
23
- "Dispatch job-runner" [shape=box];
24
- "Summarize the answer" [shape=box, style=filled, fillcolor=lightyellow];
25
- "Continue analysis or lock in?" [shape=diamond, style=filled, fillcolor=lightblue];
26
- "/skill:finishing-exploratory-data-analysis" [shape=doublecircle];
27
-
28
- "Start analysis" -> "Scope the analysis";
29
- "Scope the analysis" -> "Peek at the data";
30
- "Peek at the data" -> "Propose/ask a question";
31
- "Propose/ask a question" -> "Build script/plot";
32
- "Build script/plot" -> "light or heavy?";
33
- "light or heavy?" -> "Reread script" [label="light"];
34
- "Reread script" -> "Build script/plot" [label="issues found"];
35
- "Reread script" -> "run now, this session" [label="looks right"];
36
- "light or heavy?" -> "Dispatch script-checker (blocking)" [label="heavy"];
37
- "Dispatch script-checker (blocking)" -> "Build script/plot" [label="issues found"];
38
- "Dispatch script-checker (blocking)" -> "Dispatch job-runner" [label="clean"];
39
- "run now, this session" -> "Summarize the answer";
40
- "Dispatch job-runner" -> "Summarize the answer";
41
- "Summarize the answer" -> "Continue analysis or lock in?";
42
- "Continue analysis or lock in?" -> "Propose/ask a question" [label="next question"];
43
- "Continue analysis or lock in?" -> "/skill:finishing-exploratory-data-analysis" [label="finish analysis"];
44
- }
45
- ```
46
-
47
- (blue = User turn, yellow = Agent -> User handoff, white = Agent-only)
48
-
49
- | Stage | Turn | What it looks like | See |
50
- | --- | --- | --- | --- |
51
- | Scope the analysis | User | State the broad scope/question area for this analysis and confirm it, once per analysis, before touching data | `pre-code-checkpoints.md` (Scope the analysis) |
52
- | Peek at the data | Agent -> User | Schema/dtypes, `.head()`, a sample, eyeball raw rows, hand back for a look | `pre-code-checkpoints.md` (Peek at the data) |
53
- | Propose/ask a question | User (agent may suggest) | Confirm framing for the next question, then the user picks what to plot or check | `pre-code-checkpoints.md` (Propose/ask a question) |
54
- | Build script/plot | Agent | Write the script/plot, smoke test on a subset before scaling | `checking-and-running-scripts.md`, `plotting.md` |
55
- | light or heavy? | Agent | Judge from the smoke-test timing/size ratio | `checking-and-running-scripts.md` |
56
- | Reread script | Agent | Light: reread the script yourself before running -- no `script-checker` dispatch, loop back to the script on issues you find | `checking-and-running-scripts.md` |
57
- | Dispatch script-checker (blocking) / Dispatch job-runner | Agent | Heavy: dispatch the `script-checker` and wait; loop back to the script on findings; don't launch the `job-runner` until it's clean | `checking-and-running-scripts.md` |
58
- | Summarize the answer | Agent -> User | Interpret the figure/stats as a summary, not a conclusion -- the user decides what it means and what's next | `SKILL.md` Core principle |
59
- | Lock-in checkpoint | User | Figure/analysis seems meaningful, ready to write up or commit | `/skill:finishing-exploratory-data-analysis` |
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 a specific question/approach, it establishes the scope but
32
+ you must confirm a report name before advancing.
33
+ - `advance` condition: the user has confirmed the scope of the analysis and has
34
+ given a report name.
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 and run script`
98
+
99
+ Light path: reread the script as a sanity check, then run it in this session.
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
+ - 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.
112
+
113
+ ### `Dispatch script-checker`
114
+
115
+ Heavy path: dispatch the `script-checker` and wait, before launching.
116
+
117
+ - `subagent({agent:"script-checker",
118
+ task:"<script path + stated intent/framing>"})`.
119
+ - This must be a blocking call. wait for the `script-checker` to finish. You
120
+ don't want to surface issues after the expensive run has finished. We want to
121
+ catch all issues here.
122
+ - Two or more edits since the last `script-checker` dispatch count as one
123
+ nontrivial change on their own. Dispatch again at that point; don't wait for
124
+ it to feel big enough.
125
+ - "Issues found" loops back to `Build script/plot`, "clean" goes to `Dispatch
126
+ job-runner`.
127
+ - `advance` condition: `script-checker` has not found substantial issues, and
128
+ all issues have been handled and rechecked.
129
+
130
+ ### `Dispatch job-runner`
131
+
132
+ Heavy path continuation: launch the `job-runner` only once the
133
+ `script-checker` is clean.
134
+
135
+ - Run the `job runner` with `subagent({agent:"job-runner", task:"<script path +
136
+ output location + estimated runtime>"})`.
137
+ - Pass the runtime estimate to the `job-runner` from your smoke test in
138
+ `Light or heavy?` above rather than making it re-derive it blind.
139
+ - Register a wake instead of blocking: `subagent_wait({id, nonBlocking: true})`
140
+ after launching. Non-blocking waits allow you to continue the conversation
141
+ with the user while waiting.
142
+ - `advance` condition: `job-runner` has successfully completed the job.
143
+
144
+ ### `Summarize the answer`
145
+
146
+ - `show` the figure(s) to the user, then summarize briefly with bullets.
147
+ - Core principle: the user decides the interpretation. Continue interrogating
148
+ the user until it makes sense to you and is consistent with the dataset.
149
+ - `advance` condition: you understand the user's interpretation of the figure.
150
+
151
+ ### `Continue analysis or lock in?`
152
+
153
+ - Ask the user whether to continue with another question or lock in the
154
+ analysis; keep interrogating until they pick one. "Next question" loops back
155
+ to `Propose/ask a question`, "finish analysis" exits to the finishing skill.
156
+ - `advance` condition: the user has decided to continue the session or finish it.
157
+
158
+ ### `/skill:finishing-exploratory-data-analysis`
159
+
160
+ Figure/analysis seems meaningful, ready to write up or commit.
60
161
 
61
162
  ## Common Mistakes
62
163
 
63
164
  | Mistake | Fix |
64
165
  | --- | --- |
65
- | 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 |
66
- | Launching a heavy scale-up while the `script-checker` is still pending or has open findings | See `checking-and-running-scripts.md`'s Common Mistakes |
166
+ | 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 |
167
+ | 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 |
168
+ | 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` |
169
+ | Picking the report/subfolder name yourself instead of asking | Ask before creating it -- one name, shared by the file and its folder |
170
+ | Treating EDA as a pipeline and auto-advancing through questions without a framing check | Confirm framing per question, not once for the whole analysis |
171
+ | 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 |
172
+ | 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 |
173
+ | Skipping the raw-row eyeball and stopping at summary stats | Read a few actual rows -- aggregates hide format/unit/encoding surprises |
174
+ | 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
@@ -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 |