dirpluck
dirpluck turns a declared file-extraction intent into a reproducible ZIP archive. It is useful when “which files belong together” is a repeatable decision rather than a one-off manual choice: the files may live in one directory or several, some may change from run to run, others may be fixed references that should always travel with the same purpose, and some may already be declared by another Configuration that you want to reuse explicitly.
Instead of reconstructing that set from memory, shell history, chat history, or local convention, you describe it once in TOML. A later run resolves the same declared sources and applies the same selection rules. The result is not a backup of everything nearby; it is a package assembled for a stated purpose.
Where dirpluck fits
Gather related material scattered across directories
A useful package is often not stored under one root. A proposal may need documents from sales/, supporting research from research/, and legal material from legal/. A recurring meeting pack may draw from notes, reports, and reference material maintained by different parts of a workspace.
dirpluck can describe those locations as fixed Companions and build the package without requiring a runtime Target at all:
[companion.proposal]
path = "sales/proposal"
description = "The proposal being prepared for delivery."
include = ["*.pdf"]
[companion.research]
path = "research"
description = "Research material supporting the proposal."
include = ["*.md", "data/*.csv"]
[output]
path = "artifacts/proposal-package.zip"
if_exists = "overwrite"
dirpluck --config proposal-package
The Configuration records why those directories belong together. The directories do not need to share a parent beyond the current working-directory boundary, and no single one has to be treated as the “main” directory.
Rebuild a recurring working set
Some file sets are useful precisely because they recur. A weekly review, editorial cycle, research pass, reporting task, or other repeated workflow may need the same categories of material every time even though the file contents change.
A Configuration turns that recurring choice into a named artifact of the workflow. The next run does not have to rediscover which folders, reports, or references were used last time. --dry-run can show the resolved plan before the Archive is written, so the declared set can be checked when the workspace changes.
Prepare a focused review or handoff package
Sharing an entire repository or working directory is often unnecessary. A review may need the current document, supporting evidence, and a checklist; a handoff may need the deliverable, operating notes, and selected reference material; a code review may need source, tests, and a dependent library.
dirpluck lets the package describe that boundary explicitly. Required material can be declared with include, while known optional material can be declared separately with include_if_exists. A missing required item therefore fails instead of silently producing a package that only appears complete.
Preserve a meaningful snapshot
A snapshot does not always mean “copy everything as it exists now.” For research, writing, investigations, design work, or release preparation, the useful snapshot may be the subset that explains the state of the work: the current documents, selected source data, decisions, and any generated artifacts that happen to exist.
A Companion-only Configuration works well for this. Fixed sources describe the parts that constitute the snapshot, and optional selections can include generated material when present without turning its absence into an error. Running the same Configuration later reproduces the same definition of the snapshot, even though its contents may have changed.
For snapshots meant to accumulate, the Configuration can use timestamp-based generated output instead of repeatedly updating one fixed path:
[output]
directory = "artifacts/snapshots"
prefix = "project"
timestamp = true
Each run creates a name such as project-YYYYMMDD-HHMMSS.zip. If that generated name already exists when the run checks its destination, the run fails. If a script deliberately starts multiple runs in the same second, the caller can add --sequence N to distinguish them. dirpluck does not infer the next number or silently rename an existing output.
dirpluck does not coordinate asynchronous or parallel invocations. If multiple runs may overlap, the caller is responsible for giving them distinct output paths; for generated output, explicit --sequence N values are one way to do that. Concurrent writes to the same output path are unsupported.
Combine changing subjects with stable references
Other workflows have one or more subjects of the same kind that change from run to run. You may review different projects against the same guidelines, inspect several case directories alongside the same reference corpus, or package a batch of submissions with the same templates and instructions.
That distinction is represented directly: one [target] definition describes the shared selection rule, and the CLI supplies one or more Target directories for that run. Companions remain fixed in the Configuration. This does not add multiple Target definitions to TOML; the same Target rule is applied independently to every supplied directory.
[target]
description = "The submission currently being reviewed."
include = ["documents", "metadata.json"]
[companion.guidelines]
path = "review-guidelines"
description = "Guidelines used for every review."
include = ["*.md"]
[output]
path = "artifacts/review.zip"
if_exists = "overwrite"
dirpluck submissions/acme
The same rule can also be applied to several runtime Targets in one Archive:
dirpluck submissions/acme submissions/contoso submissions/globex
Each Root Target keeps its directory path relative to cwd in the Archive and uses the same Target selection. The same Configuration can therefore be reused for one subject or a batch without duplicating the TOML definition.
Layer another Configuration
When a related project already has its own dirpluck Configuration, you can layer that Configuration instead of copying its Target, Companion paths, or Shared patterns into the parent.
[import.shikumi]
root = ".."
configuration = "shikumi/dirpluck.toml"
Each Configuration may declare at most one import. The imported Configuration may itself import one more, so A -> B -> C forms a linear chain. There is no depth limit; only a cycle that re-enters a Configuration file already on the active chain is rejected.
Name resolution proceeds from the deepest layer outward. Each Configuration may define at most one Target, while Companions and Shared patterns are named. An outer definition with the same name shadows the inner definition as a whole; different names coexist. The result is one effective Configuration.
If the imported Configuration defines a Target and the outer layer does not, that Target becomes effective. If the outer layer also defines [target], the outer Target shadows the inner Target together with all of its Cases. The same rule applies to same-named Companions and Shared patterns.
When an imported Target survives resolution, its source directory is inferred from the Configuration location if the file is <project>/dirpluck.toml or <project>/dirpluck/<name>.toml. This allows a composition Configuration to contain only import + output.
root is relative to the Configuration file that declares the import, not to the process cwd. Absolute paths are rejected. The resolved root becomes the filesystem boundary of the directly imported layer, and the same rule repeats at every hop in the chain.
If needed, [import.<name>.companion.<name>] can still define a Companion whose path is anchored to the immediate import root. That definition participates in the same Companion-name resolution as ordinary Companions.
Cases are not selected independently per layer. After the Configuration chain has been resolved, a single CLI --case is applied to the effective Configuration. Only the outermost Root Configuration's [output] is used.
Keep LLM-assisted work reproducible
LLM-assisted work is one important instance of the same problem. During a long-running task, a human and an LLM may repeatedly need the same source material, reference documents, generated outputs, or related projects. If that set exists only in conversation history, each new session has to reconstruct it and may reconstruct it differently.
A Configuration gives both sides an inspectable statement of what belongs together. An LLM can read the file, explain why each source is present, help revise it, and use --dry-run to check the resulting plan. The durable decision remains in TOML rather than in the model’s memory of an earlier conversation.
Broad directory selections require explicit exclusions
When include or include_if_exists selects a directory, files below that directory are collected recursively. dirpluck does not treat hidden files, repository metadata, environment files, credentials, or private keys as special cases. A broad selection such as include = ["src"] or include = ["*"] therefore includes content under selected directories unless an exclude rule removes it.
Before sharing an Archive or sending it outside the workspace, review broad selections and add exclusions appropriate to that workspace. .git/, .env*, *.pem, and *.key are common examples, but no example list can identify every sensitive file. dirpluck deliberately does not infer which files are secrets or exclude them automatically.
When the same exclusion list is needed by the base selection and several Cases, or by several sources, define it once as a Shared pattern. Across a Configuration chain, names are resolved from the deepest layer outward and an outer same-named pattern shadows the inner one. Selections use the ordinary pattern name, and the reference is valid as long as it resolves in the final effective Configuration.
[shared.exclude_patterns]
python-dev = [
".git/",
".venv/",
"__pycache__/",
".pytest_cache/",
"*.egg-info/",
"*.pyc",
".DS_Store",
]
[target]
description = "The current development target."
include_if_exists = ["src", "tests", "README.md"]
exclude_pattern_refs = ["python-dev"]
if_empty = "allow"
[target.case.all]
description = "All target files except shared development artifacts."
include_if_exists = ["*"]
exclude_pattern_refs = ["python-dev"]
if_empty = "allow"
Cases do not inherit from the base selection, so exclude_pattern_refs must be written explicitly on every selection that needs it. This preserves the rule that a Case is a complete replacement while allowing long pattern arrays to be reused.
Why the model is intentionally narrow
The examples above are different uses of the same claim: one Root Configuration represents one final Archive intent. dirpluck does not expand into a graph with multiple imports or a profile-merging system. Instead, it uses one linear Configuration chain and explicit shadowing rules.
dirpluck therefore does not infer which files are important, select the newest artifact, search arbitrary directory depth, or invent relationships between sources. Those decisions stay visible in Configuration. When another Configuration is used, dirpluck does not open arbitrary paths outside cwd for individual sources; [import.<name>] explicitly names both the Configuration and the execution root being reused.
A Case is intentionally smaller than a profile system. One run may select at most one flat Case name. That Case can switch complete selections for the Target and for Companions that define the same name; Companions without that Case continue with their base selection. A Case does not add or remove sources, change Companion paths, or replace Output policy.
Required and optional material are also kept separate. include means a match is necessary for the declared intent; include_if_exists means absence is expected and acceptable. This lets a Configuration distinguish “not present today” from “the package is incomplete.”
The model in brief
Each Configuration may define at most one Target and may define named Companions and Shared patterns. Each Configuration may also import at most one other Configuration, forming a linear chain with no depth limit. dirpluck resolves same-named definitions from the deepest layer outward, with outer definitions shadowing inner ones, and produces one effective Configuration. The effective Configuration contains at most one Target and at most one Companion or Shared pattern for each name.
A Case is one flat selection variation shared by the effective Configuration. When a Target exists, it must define the selected Case; each Companion uses the same-named Case when present and otherwise falls back to its base selection. Without a Target, at least one Companion must define the selected Case.
Selections describe required and optional entries inside each source. Repeated pattern sets may be given Shared pattern names and referenced explicitly from the selections that need them. Shared-pattern references are resolved across the Configuration chain, so an outer layer may provide or override a pattern used by an inner source. One Case is selected for the effective Configuration, and only the Root Configuration owns the final Output.
For the complete TOML authoring guide, including Target and Companion forms, Cases, selection fields, and full examples, see CONFIGURATION.md. For exact matching, discovery, filesystem-boundary, Archive, Output, dry-run, and error semantics, see SPECIFICATION.md.
CLI
The supported public interface is the CLI.
When the Root Configuration's own Target survives into the effective Configuration, supply one or more DIRECTORY values. When an imported Target is effective, do not supply a positional directory.
dirpluck DIRECTORY [DIRECTORY ...]
A Configuration made only of Companions needs no positional directory:
dirpluck --config snapshot
Use one named Case with --case:
dirpluck DIRECTORY [DIRECTORY ...] --case review
For a Companion-only Configuration:
dirpluck --config snapshot --case archive
Use another Configuration with --config NAME. NAME is a filename, not an arbitrary path; the .toml suffix is optional.
dirpluck DIRECTORY [DIRECTORY ...] --config review
List Configurations discoverable from the current working directory:
dirpluck --configs
Preview the same Archive plan without creating or modifying the output:
dirpluck DIRECTORY [DIRECTORY ...] --case review --dry-run
For a Configuration that uses generated output, supply a positive sequence number only when same-second runs need an explicit distinction:
dirpluck --config snapshot --sequence 2
There are intentionally no CLI options that temporarily replace selection rules or the Output form. Those decisions belong to the Configuration so the invocation remains explainable from the same declared intent. --sequence does not replace the naming rule; it only fills the optional runtime number slot declared by generated output.
Installation
Python 3.11 or later is required. Current release: 0.5.0.
pip install dirpluck
After installation:
dirpluck --version
dirpluck has no third-party runtime dependencies.
Public interface and documentation
The CLI is dirpluck's compatibility-supported public interface. Python modules and functions inside the package remain importable for implementation structure and testing, but they are not currently a compatibility-guaranteed public Python API.
The documentation is split by purpose:
- README.md explains what dirpluck is useful for, how its model maps to those uses, and the main design boundaries.
- USAGE.md is a compact operational reference for the positive-use rules needed when running dirpluck.
- CONFIGURATION.md explains how to write the TOML Configuration and provides complete authoring examples.
- SPECIFICATION.md defines exact matching, discovery, boundary, Archive, Output, dry-run, and error semantics.
- GLOSSARY.md provides compact definitions of the domain terms used across the project.
The wheel includes only USAGE.md under dirpluck/docs/, keeping the information needed at use time alongside the installed CLI. README, CONFIGURATION, SPECIFICATION, GLOSSARY, CHANGELOG, and the document-generation infrastructure are available from the sdist or repository.
Metadata
Release files for dirpluck 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dirpluck-0.5.0.tar.gz | 123.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dirpluck-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 152.8 kB
Release files / dirpluck-0.5.0.tar.gz
| Download URL | dirpluck-0.5.0.tar.gz |
|---|---|
| Size | 123.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cf4e7a32520f1ef2587b987b9919c723b11206376472592a2c61b6b9987432e1
|
|
BLAKE2b-256 checksum How to use checksums |
a1b2be6cffce9777b0c03051ff0cf603f27c458daf31abf89fd9610646348b84
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / dirpluck-0.5.0-py3-none-any.whl
| Download URL | dirpluck-0.5.0-py3-none-any.whl |
|---|---|
| Size | 29.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8899151c7acccdaef5085451e256bd452ce14ee2e67baa1cf2167f182786c244
|
|
BLAKE2b-256 checksum How to use checksums |
44188d8ad56d7131ea543239372e726f979a64d088199694dd8726279eab12d4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|