Examples¶
Examples use test fixtures from the repository checkout. The fixtures are not included in the installed distribution, so run these commands from a checkout or replace the paths with your own Stata files. The Stata code is synthetic and intentionally unpolished: it exercises parser behavior without containing any confidential data.
1. Basic Variable Trace¶
Trace income through sample.do, which builds income from wage and transfer
components and then renames it.
Stata code (tests/fixtures/sample.do):
* Sample: build total income from wage and transfer components.
gen wages = 1200
gen transfers = 300
gen income = wages + transfers
rename income total_income
replace total_income = total_income * 1.05
label variable total_income "Total household income"
Result:
ranges: line 4 (whereincomeis created bygen income = wages + transfers)ancestors:["wages", "transfers"]-- the two variablesincomedepends oncoverage: 1.0 -- every executable line is attributed to some variable- The rename on line 5 appears in the audit inventory as an
incomereference and atotal_incomecreation. Tracetotal_incometo follow the renamed variable. provenance_chunkcontains the selected target and ancestor lifecycle as one marked, contiguous source block. It is an audit slice, not a standalone executable Stata program.
Request the human-readable provenance document¶
JSON is the CLI default. Add --format markdown to request the human-readable
document:
The complete output is:
# Variable provenance: income
Resolved lineage slice. Standalone Stata execution is not assessed.
Ordering: execution order.
Target variable: `income`
Resolved ancestor variables: `wages`, `transfers`
Lineage variables: `income`, `wages`, `transfers`
Lineage variables without lifecycle ranges:
- None
## Resolved lineage code
```stata
* [tests/fixtures/sample.do:2 | wages:created | occurrence:1]
gen wages = 1200
* [tests/fixtures/sample.do:3 | transfers:created | occurrence:1]
gen transfers = 300
* [tests/fixtures/sample.do:4 | income:created | occurrence:1]
gen income = wages + transfers
```
## Potential replication context
No unresolved blocks or project diagnostics were recorded.
The Stata comments before each source group are provenance markers generated by
do2screen-py. They identify the physical source range, selected lifecycle
effects, and execution occurrence. The source lines below each marker are copied
from LineRange.source_lines; do2screen-py does not rewrite them.
To save the document for a report, review, or downstream prompt:
Important
This output is an audit slice, not a promise that the extracted lines can be
run as a standalone do file. Dataset loading, macro definitions, control
flow, restructuring, and user-written commands may still be required. The
document says Standalone Stata execution is not assessed for this reason.
Use the structured chunk from Python¶
Use provenance_chunk when another application needs stable fields rather than
rendered Markdown:
from do2screen import trace
result = trace("tests/fixtures/sample.do", "income")
chunk = result.provenance_chunk
assert chunk is not None
print(chunk.lineage_variables)
# ['income', 'wages', 'transfers']
for statement in chunk.statements:
effects = [(effect.variable, effect.kind) for effect in statement.effects]
print(statement.range.source, statement.range.start_line, effects)
print(chunk.text) # marked Stata source only, without the Markdown narrative
To produce exactly the CLI Markdown document in Python:
from do2screen import trace
from do2screen.provenance import render_markdown
result = trace("tests/fixtures/sample.do", "income")
document = render_markdown(result)
render_markdown() is deterministic and performs no file, network, Stata, or
language-model calls. The structured statements are the rendering source of
truth.
2. Rename Chain Tracking¶
Trace new_name through rename.do, which creates old_name, modifies it,
renames it to new_name, then uses it.
Stata code (tests/fixtures/rename.do):
* Rename target and source bookkeeping.
gen old_name = 5
replace old_name = old_name + 1
rename old_name new_name
gen out = new_name * 2
Result:
ranges: line 4 (the rename createsnew_name)ancestors:["old_name"]-- the rename carries the old variable into the new name. The earlierold_namelifecycle lines remain underold_namein the audit inventory.
The rename is tracked through a generic dependency edge: old_name is the source
of the new_name definition, without rewriting earlier physical ranges.
3. Include Graph Traversal¶
Trace income through include_root.do, which includes inc/lib.do and then
uses a variable defined there.
Stata code (tests/fixtures/include_root.do):
* Include chain: the child file supplies bonus, the root consumes it.
include "inc/lib.do"
gen income = bonus + 1
Child file (tests/fixtures/inc/lib.do):
Result:
ranges: line 3 of the root file (whereincomeis created)ancestors:["bonus"]-- defined in the included filesources: twoSourceProvenanceentries, one per file, in traversal orderattributed_rangesincludes ranges from both files
The parser resolves the include path relative to the root file and attributes
code in inc/lib.do to its own source, not the root.
4. Handling Unresolved Blocks (Macros)¶
Trace educat4 through unres_macro.do, which builds variable names from a
macro inside a foreach loop.
Stata code (tests/fixtures/unres_macro.do):
* Macro-built variable in an enclosing loop: the whole block is unresolved.
foreach x in 4 5 7 {
gen educat`x' = 1
replace educat`x' = educat`x' * 2
}
Result:
ranges:[]-- the literal stringeducat4never appears; the macro`x'constructs it at runtimeunresolved_blocks: one block with reasonmacro_or_loop, covering the entireforeachblockcoverage: 0.0 -- the executable lines are in an unresolved block and cannot be attributed
This is the correct behaviour: the parser reports what it could not resolve rather than guessing or silently dropping the code.
The Markdown format keeps this uncertainty visibly separate from resolved code:
Because no literal educat4 lifecycle statement can be resolved, the document
states No selected lifecycle statements were resolved. Under
## Potential replication context, it then records the macro_or_loop reason,
source range, parser context, original statement, and source lines. An empty code
section therefore means "no resolved statement," not "the variable is proven
absent."
5. Prefix Commands¶
Trace rank through prefixes.do, which uses bysort as a command prefix.
Stata code (tests/fixtures/prefixes.do):
* Prefix commands wrap a creating command.
gen region = 1
bysort region: gen rank = 1
sort region
replace rank = rank + 1
Result:
ranges: line 3 (gen rank = 1withbysortprefix), line 5 (replace rank)- The
bysort region:prefix is stripped before classifying the inner statement as agen(creates). Thesorton line 4 does not affect a variable, so itsrestructureseffect is reported as anunsupported_effectunresolved block rather than as a variable attribution.
6. Delimiter Switching¶
Trace d1 through delimit.do, which switches between #delimit cr and
#delimit ; multiple times.
Stata code (tests/fixtures/delimit.do):
* Delimiter mode switching, including multiple switches.
gen d1 = 1
#delimit ;
gen d2 = 2;
replace d2 = d2 + 1;
#delimit cr
gen d3 = 3
#delimit ;
replace d3 = 4;
#delimit clear
gen d4 = d3 + 1
Result:
ranges: line 2 (gen d1 = 1)- The
#delimit ;blocks are parsed correctly: statements inside the block are terminated by;until#delimit cror#delimit clearresets the mode - The file enters semicolon mode twice and returns to carriage-return mode twice; the parser handles all transitions.
7. No-Follow-Parents Mode¶
Disable ancestor resolution to see only the traced variable's own ranges.
Stata code (tests/fixtures/lineage.do):
* Recursive lineage: primary <- adult <- person
gen person = 1
gen adult = person
gen primary = adult
replace primary = primary * 2
Result:
ancestors:[]-- ancestor resolution is disabledranges: lines 4 and 5 (thegenandreplaceforprimary)attributed_ranges: still contains the full audit inventory for all variables in the file
Use --no-follow-parents when you want the line ranges without the dependency
graph, for example when the downstream consumer only needs the source lines.
8. Label Tracking¶
Include label variable events in the trace by enabling the --labels flag.
Stata code (tests/fixtures/labels.do):
* Label events: label variable (value labels attach a label name, not a
* data variable, so label value is excluded from this fixture).
gen income = 1
label variable income "Monthly income"
gen other = income * 2
Result:
ranges: line 3 (gen income = 1), line 4 (label variable income)- Without
--labels, the label event on line 4 is excluded from the traced ranges (but still present inattributed_rangesas alabelledkind) - With
--labels, the resolved lineage block includes a marker such asincome:labelledfollowed by the exactlabel variablesource line.
Note
By default, label events are excluded from lifecycle ranges to match the
behaviour of do2screen (Stata). Use --labels / include_labels=True when
you need them.
9. Project Inputs¶
Project tracing accepts an explicitly ordered file list, a manifest V1 file, or a directory corpus:
Explicit files and manifest entries define execution order. Directory discovery
is deterministic but unordered, so ambiguous cross-file ancestry is omitted and
reported as a cross_file_unordered item in result.project_diagnostics.
Missing inputs may also appear there on a successful partial result. A project
with no readable roots exits with code 1 and writes no success JSON.
For explicit files and manifests, result.provenance_chunk.ordering is
"execution"; repeated includes are represented by repeated statements with
distinct occurrence_sequence values. Directory results use "per_source" and
the Markdown output explicitly warns that no global execution sequence is known.
Lineage variables that have no resolved lifecycle range appear in
lineage_variables_without_ranges rather than being presented as complete.
Repeated includes and occurrence markers¶
For ordered file lists and manifests, include bodies are replayed at each call
site. If the same child file runs twice, its selected statements appear twice in
the chunk with distinct markers such as occurrence:2 and occurrence:3. This
represents execution occurrences without duplicating the physical source in
sources or project_files.
Directory mode does not invent execution order¶
Directory discovery is lexical and deterministic, but it is not a declared Stata execution sequence. Its Markdown document therefore says:
Statements remain ordered by source and physical line. Cross-file references
that require an unknown global order are omitted from resolved ancestors and
reported in project_diagnostics, which the Markdown renderer includes under
## Potential replication context.
Target or ancestor without a resolved lifecycle range¶
If a selected lineage variable is external, constructed dynamically, or
otherwise has no resolved lifecycle statement, its name appears in
lineage_variables_without_ranges. If the target itself has no selected
statement, the code section explicitly says:
Always inspect unresolved_blocks and project_diagnostics before interpreting
that result as evidence that the variable is absent.
Project tracing requires an installed, conformant stata-registry>=0.4.0
source-driver capability because include drivers must come from the upstream
registry. Until it is published on PyPI, install it separately from the
stata-command-registry source repository. The package does not execute Stata
or make network calls while tracing.