ds — a data project, run through craft with a computed data-quality gate
The lifecycle is craft. Read it and follow it.
This file is a delta: it supplies the domain — the CLARIFY axes, the plan grammar, the lenses,
the mechanical checks, the refs, the authority text. It ships no workflow.js and restates none of
craft's mechanics.
What makes a run ds rather than plain craft is one thing: every declared data output is verified
by a computed runner, not by the agent that produced it. scripts/ds-dq.py reads the approved
plan's ## Data Outputs table and opens each artifact itself; craft's JS reads its exit code.
Write surface
Main chat clarifies, plans and dispatches. It does not do the analysis, and it never writes a .py,
.ipynb, .R, .sas, .sql or .qmd file — not by Write/Edit, and not by Bash (python3 -c,
pixi run python, inline pandas/numpy). Analysis runs in a dispatched agent. Craft's dispatch is
already structural and its judges are pinned to Explore, so this line is a rule on you, not a hook;
reach for the workflow first rather than after a refusal.
Phase 1 — CLARIFY
Craft's Phase 1, on these axes.
Loading a dataset or proposing a method before the user has stated the outcome anchors the work to
whatever data happens to be nearby. Skipping a necessary profile replaces evidence with a convenient
guess. Neither shortcut is helpful: it creates an analysis that is fast to start and expensive to
correct.
| Axis |
Establish with the user |
| Outcome |
Question, audience, decision the analysis informs, and deliverables |
| Universe |
Unit of observation, entity inclusion rule, sample period, and authoritative source when sources disagree |
| Sources |
Available data, access/location, expected refresh/vintage, and known restrictions |
| Constraints |
Replication target, required methods, deadline, compute/cost limits, and reproducibility needs |
| Evidence |
What makes each planned output credible: a check, an inspectable exhibit, a source comparison, or user judgment |
Ask in one AskUserQuestion call when answers are independent. Ask cascading questions separately:
a source choice that determines available variables must be answered before variable questions.
Three facts decide these axes, and each prevents a defect that is expensive to find later:
- A sample period and an entity universe are research choices, not properties inferred from the
first query result. A rate at the wrong grain is a different statistic with a familiar label.
- The universe predicate belongs where scope is decided. Applying it to a lookup or a denominator
can turn available values into nulls; a 43.7% null-denominator incident came from exactly that
duplicate filtering mistake.
- A criterion that cannot name evidence is a wish. The explicit
TBD (<phase>) convention is
legitimate only when profiling is the scheduled evidence-producing phase; never invent coverage.
Craft's remaining axes are taken as craft states them, with two domain bindings: craft axis 4
(observable success criteria) is answered with the DQ runner over the plan's ## Data Outputs plus
the target project's own test and lint commands when the project has them, and those strings
become mechanicalChecks verbatim; craft axis 5 (review surface) is answered by the plan's
## Review Surfaces section. The ds-dq entry is never conditional. The tests and lint entries
are omitted entirely when the project has no such command — craft's probe runs a cmd verbatim,
so an unsubstituted <the project's lint command> placeholder blocks the gate on a placeholder
instead of on data quality.
Then gather planning evidence — a read-only profile, never implementation. For each source that
materially affects the plan, collect or dispatch a profile covering: location/access, approximate
shape, columns/types, date coverage, and likely row grain/key; nulls, duplicates, type drift,
category/distribution anomalies, and likely join risks; raw size and whether source-side filtering,
caching, partitioning, or server-side computation is needed; and a small representative sample only
when access and project policy permit it. Use direct read-only inspection for one small local source;
for independent sources, dispatch read-only profilers in parallel and consolidate. Do not make a
profiling agent implement a pipeline, mutate project files, or spawn further agents. Do not pull a
full source merely to estimate it. If an answer depends on implementation, state it as a planned
evidence task rather than pretending the profile established it.
Four discovery rules run in this same planning step, each governed by its own constraint file:
- Large sources. If a source may be large (roughly 50M rows, 500 MB to ship, or described as
bulk/large/uncertain), read
${CLAUDE_PLUGIN_ROOT}/references/constraints/ds-data-pull-profile.md and follow it. The
profile must compare filtered raw and candidate aggregate/server-side paths before planning commits
to one. Do not pull a full source merely to estimate it.
- External-skill discovery. When the plan will use another skill or data provider, discover its
relevant references and examples before choosing an approach: read
${CLAUDE_PLUGIN_ROOT}/references/constraints/ds-external-skill-discovery.md and follow it.
Record the resulting ADOPT, PATCH, or GREENFIELD decision in the plan itself.
- Master datasets. For multi-output work that shares a sample, read
${CLAUDE_PLUGIN_ROOT}/references/constraints/ds-master-datasets.md. Plan the minimal
canonical analysis dataset(s), their grain and keys, and which planned outputs consume each one.
- Parameter transparency. For analytic filters or tunable thresholds, read
${CLAUDE_PLUGIN_ROOT}/references/constraints/ds-parameter-transparency.md; name one
configuration location, rationale, and treatment of convenience choices in the plan.
Phase 2 — PLAN
Craft's Phase 2. Five domain requirements on the plan:
The source/access strategy and the profile-derived risks. The plan states how each source is
reached, and the data-quality and scale risks the read-only profile actually surfaced. A risk found
at CLARIFY that reaches no plan section is a risk nothing acts on.
Reproducibility decisions, appropriate to the work: source vintages, seeds, environments and
config. R1 (fresh re-run reproducibility) is judged by the data-quality-judgement lens, so a
plan recording none of these leaves R1 with nothing to judge against.
A ## Data Outputs table, in exactly this grammar, because scripts/ds-dq.py parses it and an
output absent from it is one nothing will check:
## Data Outputs
| Path | Grain | Key Columns | Required Window |
|---|---|---|---|
| data/processed/panel.parquet | one row per firm-fiscal-year | pk: gvkey, fyear; event: gvkey, datadate | datadate: 2005-01-01..2025-12-31 |
| data/processed/industry_xwalk.csv | one row per SIC code | sic | n/a |
- Path — the produced artifact, relative to the project root.
.parquet, .csv, or .tsv;
one row per artifact the plan promises to produce.
- Grain — the declared row grain, in words. This is the claim
DQ3 verifies rather than
assumes.
- Key Columns — the declared primary key
DQ3a tests for uniqueness. Write a, b for a bare
primary key, or pk: a, b; event: c, d to also declare the coarser business/event key DQ3c
needs to catch amendments and restatements. Without an event: clause DQ3c reports N/A, and
the runner says why.
- Required Window — the sample period
COV checks, as [column: ]YYYY-MM-DD..YYYY-MM-DD.
Write n/a for an unwindowed output; COV then reports N/A with that as its reason. Naming
the column is optional only when exactly one date column exists; an ambiguous window is a FAIL,
not a pass.
A ## Review Surfaces section naming the concrete tables, figures, notebook exports,
diagnostics, or decisions the user will inspect during human review.
refs per task row and per lens — required, may be empty. Craft's spine does not validate it;
wc-probe P7 refuses an absent key in THIS file, so a live run assembled from an approved plan is
unchecked. Write refs: [] to state "no domain rules" rather than omitting the key.
Phase 3 — GOAL
Craft's Phase 3 unchanged.
Phase 4 — the craft call
The args go in the plan's <!-- craft:dispatch --> arming block, and the dispatch is craft's own
craft-dispatch.sh — never a hand-written runner line. That script owns the TIER 1 plan-lint
gate, which refuses to dispatch on a major/critical plan finding and fails CLOSED on a verdict it
cannot count; hand-rolling the invocation silently drops it. Craft owns the wait, the result handling
and the return shape too, and craft-result.sh reads the verdict. This run's projectDir is the
session repo, so craft's own run directory is already inside it and no --run-dir override applies.
There is no built-in Workflow call — the guard at
~/.claude/hooks/main-thread-guard.sh denies that tool outright.
bash ${CLAUDE_PLUGIN_ROOT}/skills/craft/scripts/craft-dispatch.sh # armed plan; or pass one
bash ${CLAUDE_PLUGIN_ROOT}/skills/craft/scripts/craft-dispatch.sh --provider codex # "run this through codex"
Forward the provider. A provider named in this skill's $ARGUMENTS, however it is spelled —
--provider codex, --dispatch codex, "run this on gpt" — is --provider codex on the line
above. --provider is the only spelling the scripts take, and both reject the others by name.
Omitting it silently runs the user's codex request on claude.
{
projectDir,
goal: "<one sentence>",
// The plan's table verbatim. Every task carries refs, empty or not. The four constraint
// aggregates are UNCONDITIONAL — every implementation task carries all four, alongside
// whichever vendored references that task's own work needs.
tasks: [
{ id: "T1",
name: "<data pull or pipeline task>",
work: "<what to build>",
writablePaths: ["<narrow>"],
acceptance: "<the criterion the verifier checks>",
refs: ["${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-constraints.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-conventions.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-analysis-constraints.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-engineering-constraints.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/etl-enforcement.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/sql-patterns.md"] },
{ id: "T2",
name: "<task whose acceptance is evidentiary>",
work: "<what to build>",
writablePaths: ["<narrow>"],
acceptance: "<the criterion the verifier checks>",
refs: ["${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-constraints.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-conventions.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-analysis-constraints.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-engineering-constraints.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/verification-patterns.md"] },
],
// The DS gate: the runner reads the approved plan's ## Data Outputs and opens each artifact
// itself. ds-dq is never conditional. tests and lint are the TARGET project's own commands,
// collected at CLARIFY, and are included ONLY when the project has them — a probe runs cmd
// verbatim, so an unsubstituted placeholder blocks the gate on a placeholder. Omit the entry.
mechanicalChecks: [
{ name: "ds-dq", cmd: "uv run --with polars python3 ${CLAUDE_PLUGIN_ROOT}/skills/ds/scripts/ds-dq.py --plan <planPath> --project-dir <projectDir>" },
{ name: "tests", cmd: "<the project's full test command — omit this entry when it has none>" },
{ name: "lint", cmd: "<the project's lint command — omit this entry when it has none>" },
],
// Judged BEFORE any implementer is dispatched; a surviving critical|major returns FAIL having
// built nothing. Cheap: a spec defect costs a few read-only agents instead of a whole round.
// Passing reviewLenses REPLACES craft's defaults, so the two defaults are spelled out here
// rather than elided — an array of two would silently drop them.
reviewLenses: [
{ key: "criteria-vs-artifacts",
agentType: "Explore",
refs: [],
prompt: "Judge the deliverable strictly against the success criteria in the plan and goal: for each criterion, is there an artifact in the working tree that satisfies it? Missing or partial satisfaction is a finding. Severity: MAJOR at minimum, CRITICAL where the unsatisfied criterion is one the deliverable cannot stand without." },
{ key: "scope-fidelity",
agentType: "Explore",
refs: [],
prompt: "Judge scope fidelity: did the changes stay inside the plan's task table and writable paths? Out-of-scope edits, unrequested features, and silently skipped plan items are findings. Severity: MAJOR at minimum, CRITICAL where an edit landed outside every declared writable path." },
{ key: "data-quality-judgement",
agentType: "Explore",
refs: ["${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-checks.md"],
prompt: "Judge ONLY the seven checks no runner can settle, against the definitions in the refs. Read them in full first. Five are MODEL-EVALUATED: M1 (approved-plan criterion compliance), UNI (universe agreement), DEN (every rate states its denominator), DEL (coverage improved because the base shrank), R1 (fresh re-run reproducibility). Report each of those five as MODEL-EVALUATED with the evidence you actually read — never as PASS, which presents a judgement as a computation. Two more are yours as well: DQ4 (row-count traceability) and DQ6 (output-first shape before/after). The runner emits a line for them only because ENUM requires one; it computes neither and emits `always N/A`, and an N/A never sets its non-zero exit — so if you do not judge them, nothing does. `always N/A` is not a third kind of pass. Report DQ4 and DQ6 as dispositioned against task-local evidence — the input → transform → output count chain for DQ4, the before/after shape for DQ6 — never as checked. Findings: a check whose judgement you cannot support with evidence you actually read, since unsupported is a finding and never a pass; M1 — a plan criterion the outputs do not meet; UNI — sources admitting different entities; DEN — a reported rate with no stated denominator; DEL — a coverage improvement not attributable to the base shrinking; R1 — reproducibility not established by the vintages, seeds, environment and config the plan records; DQ4 — a row-count chain that cannot be traced input → transform → output; DQ6 — no before/after shape for the transform. Severity: a failed or unsupported judgement on any of the seven is `major` at minimum, and `critical` where the defect invalidates the output's stated grain, universe or inference — never `minor`, which would leave the gate passing over a real universe defect." },
{ key: "methodology",
agentType: "Explore",
refs: ["${CLAUDE_PLUGIN_ROOT}/skills/ds/references/verification-patterns.md"],
prompt: "Judge only the method and the evidence behind it, against the patterns in the refs. Read them in full first. Findings: a statistic computed at a grain other than the one the plan declared, a universe predicate applied where scope was not meant to be decided, a claim whose evidence is read from code rather than observed from a run, and an output the plan promised that nothing checks. Severity: each of these four is `major` at minimum, and `critical` where the defect invalidates the output's stated grain, universe or inference — never `minor`, which would leave the gate passing over a real methodology defect." },
// This lens does NOT pin Explore. Explore is a built-in agent with a PREDEFINED prompt that no
// preloaded skill reaches, and it skips the CLAUDE.md hierarchy — so a constraint-indexed
// judgement dispatched there is graded from memory of constraint ids it was never given. That
// is the cost the four lenses above pay for Explore's structural read-only guarantee.
// ds-reviewer's body is a file this repo controls, and it is read-only by tools allowlist AND
// by tests/agent-contract.test.mjs — the same structural property, in an agent whose prompt can
// be told what it is grading. The four aggregates are not vendored into any skill: they reach
// this lens as refs, from their one canonical home.
{ key: "ds-constraints",
agentType: "ds-reviewer",
refs: ["${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-constraints.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-conventions.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-analysis-constraints.md",
"${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-engineering-constraints.md"],
prompt: "Grade the implemented code and outputs against the indexed constraints in your refs — C1-C6, V1-V9, A1-A6, E1-E6, read in full first — and against the two no regex reaches: every reported rate states its denominator, and the row-count chain traces input → transform → output. Grade only the constraints the task actually touches; an engineering constraint applied to a pure analysis task is a wrong finding that costs a round. Report every finding with the file, the line and the quoted code, naming the constraint id, and list every id you considered including those you judged satisfied. NEVER report a constraint judgement as a computation — it is MODEL-EVALUATED, with the evidence you actually read. Severity: `major` at minimum, `critical` where the defect invalidates the output's stated grain, universe or inference, never `minor`." },
],
authorityExtra: [
"IRON LAW OF DS PLANNING — ask before you look; plan only from evidence you actually gathered.",
"IRON LAW OF DS VERIFICATION — no check result without the runner's own output. A mechanical check reported from reading the code, or an N/A justified by a reason the model composed, is the model certifying its own enumeration. Every mechanical result is a line ${CLAUDE_PLUGIN_ROOT}/skills/ds/scripts/ds-dq.py emitted, quoted as emitted.",
"Never report M1, UNI, DEN, DEL or R1 as PASS. They are MODEL-EVALUATED judgements and are reported as such, with the evidence read.",
"DQ4 and DQ6 are `always N/A` from the runner, and `always N/A` is not a third kind of pass — the runner emits a line for them only because ENUM requires one, and an N/A never sets its non-zero exit. Both are dispositioned against task-local evidence, exactly like the MODEL-EVALUATED rows: the input → transform → output count chain for DQ4, the before/after shape for DQ6. Never read their N/A as `the runner checked this`.",
"An artifact absent from the plan's ## Data Outputs table is one nothing will check and cannot be claimed as verified. Do not verify an output the table never declared.",
"The analysis is done by dispatched agents. Main chat writes no .py, .ipynb, .R, .sas, .sql or .qmd file, by any tool.",
"Standing DS doer authority — every implementation task follows the indexed constraints in the four aggregates under ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/: ds-common-constraints.md (C1-C6), ds-common-conventions.md (V1-V9 — assumption-over-evidence, deferred verification, statistical validity, P-hacking prevention, sample-selection documentation), ds-analysis-constraints.md (A1-A6 — robustness checks, standard-error specification, visualization integrity, table-figure pairing, chart typography, chart colour) and ds-engineering-constraints.md (E1-E6 — determinism and seeds, schema contracts, join audits with row counts and match rates, idempotency, loud error handling, native document input), each indexing the self-contained files under ${CLAUDE_PLUGIN_ROOT}/references/constraints/. All four are named in every implementation task's refs, and refs are contractual reads, not a reading list: read all four in full before writing code. ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-checks.md stays a separate load.",
"Rules: ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-checks.md defines all thirteen checks; ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/etl-enforcement.md governs pipelines; ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/sql-patterns.md governs data pulls; ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/verification-patterns.md governs evidence; ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/competing-hypothesis.md governs debugging.",
].join("\n"),
implementerAgentType: "ds", // the doer's own prompt replaces Claude Code's software-engineering one, which frames a dataset as a codebase
verifierAgentType: "Explore",
}
implementerAgentType names ds because the default agent carries Claude Code's
software-engineering system prompt, and the deliverable here is a dataset, a table and a figure a
human reads — not a codebase. An agent is justified only by a custom prompt, hooks or preloaded
skills; ds earns it on the first alone — the constraint aggregates reach it as task refs, not as
a preloaded skill.
verifierAgentType and the four generic lenses pin Explore because it has no Edit and no Write: a
judge that structurally cannot modify the tree beats a prompt asking it not to. The ds-constraints
lens buys the same property a different way — ds-reviewer is read-only by tools
allowlist — because Explore's prompt is predefined and no preloaded skill or CLAUDE.md reaches it;
that lens carries the four aggregates in its own refs.
Add ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/competing-hypothesis.md to a task's refs when
that task is a diagnosis rather than a build. authorityExtra names it; refs is what makes an
implementer read it in full.
The runner's own contract suite lives at
${CLAUDE_PLUGIN_ROOT}/skills/ds/scripts/ds_dq_test.py; add it as a mechanicalChecks entry on
any run that touches the runner.
Phase 5 — HUMAN REVIEW
Craft's Phase 5 unchanged, over the plan's ## Review Surfaces. A clean technical verification is
evidence for that conversation, not human acceptance.
Red flags
| Situation |
Wrong move |
Right move |
| Sizing a source |
pull the whole thing to see how big it is |
the transfer is the failure you are meant to prevent — profile filtered counts and aggregate candidates read-only |
| Profiling a source |
treat a head sample as the profile |
nulls, grain failures and type drift live outside the head — profile shape, tail/coverage, keys and quality signals |
| Naming a produced artifact |
name it in prose |
a runner given prose cannot open it, key it, or window it — add its row to ## Data Outputs before approval |
| The DQ result |
let the implementer report it |
the doer cannot see the assumption it made in both places — the runner is a mechanicalCheck and the JS reads its exit code |
M1/UNI/DEN/DEL/R1 |
report them as PASS |
that presents a judgement as a computation — MODEL-EVALUATED with the evidence read |
DQ4/DQ6 reported N/A |
read the N/A as the runner having checked them |
always N/A is not a third kind of pass — the runner computes neither and an N/A never sets its non-zero exit; disposition both against task-local evidence, exactly like the MODEL-EVALUATED rows |
| A judgement that depends on the constraint index |
dispatch a built-in agent (Explore, Plan, general-purpose) |
their prompts are predefined, no preloaded skill reaches them and they skip the CLAUDE.md hierarchy, so the constraints are graded from memory — dispatch a custom agent whose body you control, like ds-reviewer |
| Handing a doer the constraint aggregates |
name the four paths in the task prompt's prose, or copy the aggregates into a skill |
prose is discretionary and a copy is a second source of truth tests/constraints-no-duplication.test.ts fails on — put the four canonical paths in the task's refs, which craft defines as reads the doer owes in full |
| Project state |
write a SPEC.md, STATE.md or LEARNINGS.md |
competing state makes progress ambiguous — the approved plan is the authority and craft hashes it |
| Something craft does not obviously do |
write a ds/workflow.js |
ask which craft parameter is missing — mechanicalChecks is what makes the DQ runner the gate |
1---2name: ds3description: ALWAYS use for ANY substantive empirical task whose output is a dataset, table, figure or number - "analyze this data", "build the panel", "merge these datasets", "run the regression", "profile this dataset", "clean this file up for analysis", "make me a summary table", "pull the data and check X", "replicate this paper's table 2", "build the ETL pipeline", "/ds". Use proactively even when the user asks casually and never says "analysis". NEGATIVE ROUTING: grading empirical work that already exists is the ds-reviewer agent; writing the results up as prose is writing-econ; spreadsheet file mechanics - reading, writing or reformatting an .xlsx - is xlsx, while building, merging or modelling the dataset inside it is ds; a one-line lookup against a file already in hand is answered inline, since this skill runs a full clarify/plan/dispatch/human-review lifecycle.4---56# ds — a data project, run through craft with a computed data-quality gate78The lifecycle is [craft](${CLAUDE_PLUGIN_ROOT}/skills/craft/SKILL.md). Read it and follow it.9This file is a **delta**: it supplies the domain — the CLARIFY axes, the plan grammar, the lenses,10the mechanical checks, the refs, the authority text. It ships no `workflow.js` and restates none of11craft's mechanics.1213What makes a run `ds` rather than plain craft is one thing: **every declared data output is verified14by a computed runner, not by the agent that produced it.** `scripts/ds-dq.py` reads the approved15plan's `## Data Outputs` table and opens each artifact itself; craft's JS reads its exit code.1617## Write surface1819Main chat clarifies, plans and dispatches. It does not do the analysis, and it never writes a `.py`,20`.ipynb`, `.R`, `.sas`, `.sql` or `.qmd` file — not by Write/Edit, and not by Bash (`python3 -c`,21`pixi run python`, inline pandas/numpy). Analysis runs in a dispatched agent. Craft's dispatch is22already structural and its judges are pinned to `Explore`, so this line is a rule on you, not a hook;23reach for the workflow first rather than after a refusal.2425## Phase 1 — CLARIFY2627Craft's Phase 1, on these axes.2829<EXTREMELY-IMPORTANT>30**ASK BEFORE YOU LOOK; PLAN ONLY FROM EVIDENCE YOU ACTUALLY GATHERED. This is not negotiable.**3132Loading a dataset or proposing a method before the user has stated the outcome anchors the work to33whatever data happens to be nearby. Skipping a necessary profile replaces evidence with a convenient34guess. Neither shortcut is helpful: it creates an analysis that is fast to start and expensive to35correct.36</EXTREMELY-IMPORTANT>3738| Axis | Establish with the user |39|---|---|40| Outcome | Question, audience, decision the analysis informs, and deliverables |41| Universe | Unit of observation, entity inclusion rule, sample period, and authoritative source when sources disagree |42| Sources | Available data, access/location, expected refresh/vintage, and known restrictions |43| Constraints | Replication target, required methods, deadline, compute/cost limits, and reproducibility needs |44| Evidence | What makes each planned output credible: a check, an inspectable exhibit, a source comparison, or user judgment |4546Ask in one `AskUserQuestion` call when answers are independent. Ask cascading questions separately:47a source choice that determines available variables must be answered before variable questions.4849Three facts decide these axes, and each prevents a defect that is expensive to find later:5051- A sample period and an entity universe are **research choices**, not properties inferred from the52 first query result. A rate at the wrong grain is a different statistic with a familiar label.53- The universe predicate belongs where scope is decided. Applying it to a lookup or a denominator54 can turn available values into nulls; a 43.7% null-denominator incident came from exactly that55 duplicate filtering mistake.56- A criterion that **cannot name evidence** is a wish. The explicit `TBD (<phase>)` convention is57 legitimate only when profiling is the scheduled evidence-producing phase; never invent coverage.5859Craft's remaining axes are taken as craft states them, with two domain bindings: craft axis 460(observable success criteria) is answered with the DQ runner over the plan's `## Data Outputs` plus61the target project's own test and lint commands **when the project has them**, and those strings62become `mechanicalChecks` verbatim; craft axis 5 (review surface) is answered by the plan's63`## Review Surfaces` section. The `ds-dq` entry is never conditional. The `tests` and `lint` entries64are **omitted entirely when the project has no such command** — craft's probe runs a `cmd` verbatim,65so an unsubstituted `<the project's lint command>` placeholder blocks the gate on a placeholder66instead of on data quality.6768Then gather planning evidence — **a read-only profile, never implementation**. For each source that69materially affects the plan, collect or dispatch a profile covering: location/access, approximate70shape, columns/types, date coverage, and likely row grain/key; nulls, duplicates, type drift,71category/distribution anomalies, and likely join risks; raw size and whether source-side filtering,72caching, partitioning, or server-side computation is needed; and a small representative sample only73when access and project policy permit it. Use direct read-only inspection for one small local source;74for independent sources, dispatch read-only profilers in parallel and consolidate. Do not make a75profiling agent implement a pipeline, mutate project files, or spawn further agents. Do not pull a76full source merely to estimate it. If an answer depends on implementation, state it as a planned77evidence task rather than pretending the profile established it.7879Four discovery rules run in this same planning step, each governed by its own constraint file:8081- **Large sources.** If a source may be large (roughly 50M rows, 500 MB to ship, or described as82 bulk/large/uncertain), read83 `${CLAUDE_PLUGIN_ROOT}/references/constraints/ds-data-pull-profile.md` and follow it. The84 profile must compare filtered raw and candidate aggregate/server-side paths before planning commits85 to one. Do not pull a full source merely to estimate it.86- **External-skill discovery.** When the plan will use another skill or data provider, discover its87 relevant references and examples before choosing an approach: read88 `${CLAUDE_PLUGIN_ROOT}/references/constraints/ds-external-skill-discovery.md` and follow it.89 Record the resulting ADOPT, PATCH, or GREENFIELD decision in the plan itself.90- **Master datasets.** For multi-output work that shares a sample, read91 `${CLAUDE_PLUGIN_ROOT}/references/constraints/ds-master-datasets.md`. Plan the minimal92 canonical analysis dataset(s), their grain and keys, and which planned outputs consume each one.93- **Parameter transparency.** For analytic filters or tunable thresholds, read94 `${CLAUDE_PLUGIN_ROOT}/references/constraints/ds-parameter-transparency.md`; name one95 configuration location, rationale, and treatment of convenience choices in the plan.9697## Phase 2 — PLAN9899Craft's Phase 2. Five domain requirements on the plan:100101- **The source/access strategy and the profile-derived risks.** The plan states how each source is102 reached, and the data-quality and scale risks the read-only profile actually surfaced. A risk found103 at CLARIFY that reaches no plan section is a risk nothing acts on.104- **Reproducibility decisions**, appropriate to the work: source vintages, seeds, environments and105 config. `R1` (fresh re-run reproducibility) is judged by the `data-quality-judgement` lens, so a106 plan recording none of these leaves `R1` with nothing to judge against.107- **A `## Data Outputs` table**, in exactly this grammar, because `scripts/ds-dq.py` parses it and an108 output absent from it is one nothing will check:109110 ```markdown111 ## Data Outputs112113 | Path | Grain | Key Columns | Required Window |114 |---|---|---|---|115 | data/processed/panel.parquet | one row per firm-fiscal-year | pk: gvkey, fyear; event: gvkey, datadate | datadate: 2005-01-01..2025-12-31 |116 | data/processed/industry_xwalk.csv | one row per SIC code | sic | n/a |117 ```118119 - **Path** — the produced artifact, relative to the project root. `.parquet`, `.csv`, or `.tsv`;120 one row per artifact the plan promises to produce.121 - **Grain** — the declared row grain, in words. This is the claim `DQ3` verifies rather than122 assumes.123 - **Key Columns** — the declared primary key `DQ3a` tests for uniqueness. Write `a, b` for a bare124 primary key, or `pk: a, b; event: c, d` to also declare the coarser business/event key `DQ3c`125 needs to catch amendments and restatements. Without an `event:` clause `DQ3c` reports `N/A`, and126 the runner says why.127 - **Required Window** — the sample period `COV` checks, as `[column: ]YYYY-MM-DD..YYYY-MM-DD`.128 Write `n/a` for an unwindowed output; `COV` then reports `N/A` with that as its reason. Naming129 the column is optional only when exactly one date column exists; an ambiguous window is a `FAIL`,130 not a pass.131132- **A `## Review Surfaces` section** naming the concrete tables, figures, notebook exports,133 diagnostics, or decisions the user will inspect during human review.134- **`refs` per task row and per lens** — required, may be empty. Craft's spine does not validate it;135 `wc-probe` P7 refuses an absent key in THIS file, so a live run assembled from an approved plan is136 unchecked. Write `refs: []` to state "no domain rules" rather than omitting the key.137138## Phase 3 — GOAL139140Craft's Phase 3 unchanged.141142## Phase 4 — the craft call143144The args go in the plan's `<!-- craft:dispatch -->` arming block, and the dispatch is **craft's own145`craft-dispatch.sh`** — never a hand-written runner line. That script owns the TIER 1 plan-lint146gate, which refuses to dispatch on a `major`/`critical` plan finding and fails CLOSED on a verdict it147cannot count; hand-rolling the invocation silently drops it. Craft owns the wait, the result handling148and the return shape too, and `craft-result.sh` reads the verdict. This run's `projectDir` is the149session repo, so craft's own run directory is already inside it and no `--run-dir` override applies.150There is no built-in `Workflow` call — the guard at151`~/.claude/hooks/main-thread-guard.sh` denies that tool outright.152153```bash154bash ${CLAUDE_PLUGIN_ROOT}/skills/craft/scripts/craft-dispatch.sh # armed plan; or pass one155bash ${CLAUDE_PLUGIN_ROOT}/skills/craft/scripts/craft-dispatch.sh --provider codex # "run this through codex"156```157158**Forward the provider.** A provider named in this skill's `$ARGUMENTS`, however it is spelled —159`--provider codex`, `--dispatch codex`, "run this on gpt" — is `--provider codex` on the line160above. `--provider` is the only spelling the scripts take, and both reject the others by name.161Omitting it silently runs the user's codex request on claude.162163```js164{165 projectDir,166 goal: "<one sentence>",167168 // The plan's table verbatim. Every task carries refs, empty or not. The four constraint169 // aggregates are UNCONDITIONAL — every implementation task carries all four, alongside170 // whichever vendored references that task's own work needs.171 tasks: [172 { id: "T1",173 name: "<data pull or pipeline task>",174 work: "<what to build>",175 writablePaths: ["<narrow>"],176 acceptance: "<the criterion the verifier checks>",177 refs: ["${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-constraints.md",178 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-conventions.md",179 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-analysis-constraints.md",180 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-engineering-constraints.md",181 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/etl-enforcement.md",182 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/sql-patterns.md"] },183184 { id: "T2",185 name: "<task whose acceptance is evidentiary>",186 work: "<what to build>",187 writablePaths: ["<narrow>"],188 acceptance: "<the criterion the verifier checks>",189 refs: ["${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-constraints.md",190 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-conventions.md",191 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-analysis-constraints.md",192 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-engineering-constraints.md",193 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/verification-patterns.md"] },194 ],195196 // The DS gate: the runner reads the approved plan's ## Data Outputs and opens each artifact197 // itself. ds-dq is never conditional. tests and lint are the TARGET project's own commands,198 // collected at CLARIFY, and are included ONLY when the project has them — a probe runs cmd199 // verbatim, so an unsubstituted placeholder blocks the gate on a placeholder. Omit the entry.200 mechanicalChecks: [201 { name: "ds-dq", cmd: "uv run --with polars python3 ${CLAUDE_PLUGIN_ROOT}/skills/ds/scripts/ds-dq.py --plan <planPath> --project-dir <projectDir>" },202 { name: "tests", cmd: "<the project's full test command — omit this entry when it has none>" },203 { name: "lint", cmd: "<the project's lint command — omit this entry when it has none>" },204 ],205206 // Judged BEFORE any implementer is dispatched; a surviving critical|major returns FAIL having207 // built nothing. Cheap: a spec defect costs a few read-only agents instead of a whole round.208 // Passing reviewLenses REPLACES craft's defaults, so the two defaults are spelled out here209 // rather than elided — an array of two would silently drop them.210 reviewLenses: [211 { key: "criteria-vs-artifacts",212 agentType: "Explore",213 refs: [],214 prompt: "Judge the deliverable strictly against the success criteria in the plan and goal: for each criterion, is there an artifact in the working tree that satisfies it? Missing or partial satisfaction is a finding. Severity: MAJOR at minimum, CRITICAL where the unsatisfied criterion is one the deliverable cannot stand without." },215216 { key: "scope-fidelity",217 agentType: "Explore",218 refs: [],219 prompt: "Judge scope fidelity: did the changes stay inside the plan's task table and writable paths? Out-of-scope edits, unrequested features, and silently skipped plan items are findings. Severity: MAJOR at minimum, CRITICAL where an edit landed outside every declared writable path." },220221 { key: "data-quality-judgement",222 agentType: "Explore",223 refs: ["${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-checks.md"],224 prompt: "Judge ONLY the seven checks no runner can settle, against the definitions in the refs. Read them in full first. Five are MODEL-EVALUATED: M1 (approved-plan criterion compliance), UNI (universe agreement), DEN (every rate states its denominator), DEL (coverage improved because the base shrank), R1 (fresh re-run reproducibility). Report each of those five as MODEL-EVALUATED with the evidence you actually read — never as PASS, which presents a judgement as a computation. Two more are yours as well: DQ4 (row-count traceability) and DQ6 (output-first shape before/after). The runner emits a line for them only because ENUM requires one; it computes neither and emits `always N/A`, and an N/A never sets its non-zero exit — so if you do not judge them, nothing does. `always N/A` is not a third kind of pass. Report DQ4 and DQ6 as dispositioned against task-local evidence — the input → transform → output count chain for DQ4, the before/after shape for DQ6 — never as checked. Findings: a check whose judgement you cannot support with evidence you actually read, since unsupported is a finding and never a pass; M1 — a plan criterion the outputs do not meet; UNI — sources admitting different entities; DEN — a reported rate with no stated denominator; DEL — a coverage improvement not attributable to the base shrinking; R1 — reproducibility not established by the vintages, seeds, environment and config the plan records; DQ4 — a row-count chain that cannot be traced input → transform → output; DQ6 — no before/after shape for the transform. Severity: a failed or unsupported judgement on any of the seven is `major` at minimum, and `critical` where the defect invalidates the output's stated grain, universe or inference — never `minor`, which would leave the gate passing over a real universe defect." },225226 { key: "methodology",227 agentType: "Explore",228 refs: ["${CLAUDE_PLUGIN_ROOT}/skills/ds/references/verification-patterns.md"],229 prompt: "Judge only the method and the evidence behind it, against the patterns in the refs. Read them in full first. Findings: a statistic computed at a grain other than the one the plan declared, a universe predicate applied where scope was not meant to be decided, a claim whose evidence is read from code rather than observed from a run, and an output the plan promised that nothing checks. Severity: each of these four is `major` at minimum, and `critical` where the defect invalidates the output's stated grain, universe or inference — never `minor`, which would leave the gate passing over a real methodology defect." },230231 // This lens does NOT pin Explore. Explore is a built-in agent with a PREDEFINED prompt that no232 // preloaded skill reaches, and it skips the CLAUDE.md hierarchy — so a constraint-indexed233 // judgement dispatched there is graded from memory of constraint ids it was never given. That234 // is the cost the four lenses above pay for Explore's structural read-only guarantee.235 // ds-reviewer's body is a file this repo controls, and it is read-only by tools allowlist AND236 // by tests/agent-contract.test.mjs — the same structural property, in an agent whose prompt can237 // be told what it is grading. The four aggregates are not vendored into any skill: they reach238 // this lens as refs, from their one canonical home.239 { key: "ds-constraints",240 agentType: "ds-reviewer",241 refs: ["${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-constraints.md",242 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-common-conventions.md",243 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-analysis-constraints.md",244 "${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-engineering-constraints.md"],245 prompt: "Grade the implemented code and outputs against the indexed constraints in your refs — C1-C6, V1-V9, A1-A6, E1-E6, read in full first — and against the two no regex reaches: every reported rate states its denominator, and the row-count chain traces input → transform → output. Grade only the constraints the task actually touches; an engineering constraint applied to a pure analysis task is a wrong finding that costs a round. Report every finding with the file, the line and the quoted code, naming the constraint id, and list every id you considered including those you judged satisfied. NEVER report a constraint judgement as a computation — it is MODEL-EVALUATED, with the evidence you actually read. Severity: `major` at minimum, `critical` where the defect invalidates the output's stated grain, universe or inference, never `minor`." },246 ],247248 authorityExtra: [249 "IRON LAW OF DS PLANNING — ask before you look; plan only from evidence you actually gathered.",250 "IRON LAW OF DS VERIFICATION — no check result without the runner's own output. A mechanical check reported from reading the code, or an N/A justified by a reason the model composed, is the model certifying its own enumeration. Every mechanical result is a line ${CLAUDE_PLUGIN_ROOT}/skills/ds/scripts/ds-dq.py emitted, quoted as emitted.",251 "Never report M1, UNI, DEN, DEL or R1 as PASS. They are MODEL-EVALUATED judgements and are reported as such, with the evidence read.",252 "DQ4 and DQ6 are `always N/A` from the runner, and `always N/A` is not a third kind of pass — the runner emits a line for them only because ENUM requires one, and an N/A never sets its non-zero exit. Both are dispositioned against task-local evidence, exactly like the MODEL-EVALUATED rows: the input → transform → output count chain for DQ4, the before/after shape for DQ6. Never read their N/A as `the runner checked this`.",253 "An artifact absent from the plan's ## Data Outputs table is one nothing will check and cannot be claimed as verified. Do not verify an output the table never declared.",254 "The analysis is done by dispatched agents. Main chat writes no .py, .ipynb, .R, .sas, .sql or .qmd file, by any tool.",255 "Standing DS doer authority — every implementation task follows the indexed constraints in the four aggregates under ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/: ds-common-constraints.md (C1-C6), ds-common-conventions.md (V1-V9 — assumption-over-evidence, deferred verification, statistical validity, P-hacking prevention, sample-selection documentation), ds-analysis-constraints.md (A1-A6 — robustness checks, standard-error specification, visualization integrity, table-figure pairing, chart typography, chart colour) and ds-engineering-constraints.md (E1-E6 — determinism and seeds, schema contracts, join audits with row counts and match rates, idempotency, loud error handling, native document input), each indexing the self-contained files under ${CLAUDE_PLUGIN_ROOT}/references/constraints/. All four are named in every implementation task's refs, and refs are contractual reads, not a reading list: read all four in full before writing code. ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-checks.md stays a separate load.",256 "Rules: ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/ds-checks.md defines all thirteen checks; ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/etl-enforcement.md governs pipelines; ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/sql-patterns.md governs data pulls; ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/verification-patterns.md governs evidence; ${CLAUDE_PLUGIN_ROOT}/skills/ds/references/competing-hypothesis.md governs debugging.",257 ].join("\n"),258259 implementerAgentType: "ds", // the doer's own prompt replaces Claude Code's software-engineering one, which frames a dataset as a codebase260 verifierAgentType: "Explore",261}262```263264`implementerAgentType` names `ds` because the default agent carries Claude Code's265software-engineering system prompt, and the deliverable here is a dataset, a table and a figure a266human reads — not a codebase. An agent is justified only by a custom prompt, hooks or preloaded267skills; `ds` earns it on the first alone — the constraint aggregates reach it as task `refs`, not as268a preloaded skill.269270`verifierAgentType` and the four generic lenses pin `Explore` because it has no Edit and no Write: a271judge that structurally cannot modify the tree beats a prompt asking it not to. The `ds-constraints`272lens buys the same property a different way — `ds-reviewer` is read-only by tools273allowlist — because Explore's prompt is predefined and no preloaded skill or CLAUDE.md reaches it;274that lens carries the four aggregates in its own `refs`.275276Add `${CLAUDE_PLUGIN_ROOT}/skills/ds/references/competing-hypothesis.md` to a task's `refs` when277that task is a diagnosis rather than a build. `authorityExtra` names it; `refs` is what makes an278implementer read it in full.279280The runner's own contract suite lives at281`${CLAUDE_PLUGIN_ROOT}/skills/ds/scripts/ds_dq_test.py`; add it as a `mechanicalChecks` entry on282any run that touches the runner.283284## Phase 5 — HUMAN REVIEW285286Craft's Phase 5 unchanged, over the plan's `## Review Surfaces`. A clean technical verification is287evidence for that conversation, not human acceptance.288289## Red flags290291| Situation | Wrong move | Right move |292|---|---|---|293| Sizing a source | pull the whole thing to see how big it is | the transfer is the failure you are meant to prevent — profile filtered counts and aggregate candidates read-only |294| Profiling a source | treat a head sample as the profile | nulls, grain failures and type drift live outside the head — profile shape, tail/coverage, keys and quality signals |295| Naming a produced artifact | name it in prose | a runner given prose cannot open it, key it, or window it — add its row to `## Data Outputs` before approval |296| The DQ result | let the implementer report it | the doer cannot see the assumption it made in both places — the runner is a `mechanicalCheck` and the JS reads its exit code |297| `M1`/`UNI`/`DEN`/`DEL`/`R1` | report them as `PASS` | that presents a judgement as a computation — `MODEL-EVALUATED` with the evidence read |298| `DQ4`/`DQ6` reported `N/A` | read the `N/A` as the runner having checked them | `always N/A` is not a third kind of pass — the runner computes neither and an `N/A` never sets its non-zero exit; disposition both against task-local evidence, exactly like the MODEL-EVALUATED rows |299| A judgement that depends on the constraint index | dispatch a built-in agent (`Explore`, `Plan`, `general-purpose`) | their prompts are predefined, no preloaded skill reaches them and they skip the CLAUDE.md hierarchy, so the constraints are graded from memory — dispatch a custom agent whose body you control, like `ds-reviewer` |300| Handing a doer the constraint aggregates | name the four paths in the task prompt's prose, or copy the aggregates into a skill | prose is discretionary and a copy is a second source of truth `tests/constraints-no-duplication.test.ts` fails on — put the four canonical paths in the task's `refs`, which craft defines as reads the doer owes in full |301| Project state | write a `SPEC.md`, `STATE.md` or `LEARNINGS.md` | competing state makes progress ambiguous — the approved plan is the authority and craft hashes it |302| Something craft does not obviously do | write a `ds/workflow.js` | ask which craft parameter is missing — `mechanicalChecks` is what makes the DQ runner the gate |