ast-pluckit 0.1.0a1__tar.gz

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.
Files changed (34) hide show
  1. ast_pluckit-0.1.0a1/.gitignore +17 -0
  2. ast_pluckit-0.1.0a1/CHANGELOG.md +86 -0
  3. ast_pluckit-0.1.0a1/LICENSE +21 -0
  4. ast_pluckit-0.1.0a1/PKG-INFO +376 -0
  5. ast_pluckit-0.1.0a1/README.md +335 -0
  6. ast_pluckit-0.1.0a1/pyproject.toml +98 -0
  7. ast_pluckit-0.1.0a1/src/pluckit/__init__.py +33 -0
  8. ast_pluckit-0.1.0a1/src/pluckit/_context.py +81 -0
  9. ast_pluckit-0.1.0a1/src/pluckit/_paths.py +57 -0
  10. ast_pluckit-0.1.0a1/src/pluckit/_sql.py +224 -0
  11. ast_pluckit-0.1.0a1/src/pluckit/cli.py +806 -0
  12. ast_pluckit-0.1.0a1/src/pluckit/mutation.py +222 -0
  13. ast_pluckit-0.1.0a1/src/pluckit/mutations.py +680 -0
  14. ast_pluckit-0.1.0a1/src/pluckit/plucker.py +121 -0
  15. ast_pluckit-0.1.0a1/src/pluckit/plugins/__init__.py +4 -0
  16. ast_pluckit-0.1.0a1/src/pluckit/plugins/base.py +62 -0
  17. ast_pluckit-0.1.0a1/src/pluckit/plugins/viewer.py +860 -0
  18. ast_pluckit-0.1.0a1/src/pluckit/selection.py +600 -0
  19. ast_pluckit-0.1.0a1/src/pluckit/selectors.py +366 -0
  20. ast_pluckit-0.1.0a1/src/pluckit/source.py +39 -0
  21. ast_pluckit-0.1.0a1/src/pluckit/types.py +50 -0
  22. ast_pluckit-0.1.0a1/tests/conftest.py +75 -0
  23. ast_pluckit-0.1.0a1/tests/plugins/__init__.py +0 -0
  24. ast_pluckit-0.1.0a1/tests/plugins/test_viewer.py +382 -0
  25. ast_pluckit-0.1.0a1/tests/test_cli.py +589 -0
  26. ast_pluckit-0.1.0a1/tests/test_mutations.py +412 -0
  27. ast_pluckit-0.1.0a1/tests/test_paths.py +91 -0
  28. ast_pluckit-0.1.0a1/tests/test_plucker.py +95 -0
  29. ast_pluckit-0.1.0a1/tests/test_plucker_context.py +42 -0
  30. ast_pluckit-0.1.0a1/tests/test_plugin_base.py +201 -0
  31. ast_pluckit-0.1.0a1/tests/test_selection.py +167 -0
  32. ast_pluckit-0.1.0a1/tests/test_selection_plugins.py +73 -0
  33. ast_pluckit-0.1.0a1/tests/test_selectors.py +85 -0
  34. ast_pluckit-0.1.0a1/tests/test_source.py +30 -0
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .eggs/
7
+
8
+ # mkdocs build output
9
+ site/
10
+
11
+ # Training data is tracked via git lfs (see .gitattributes)
12
+ # Regenerate with: bash training/run_pipeline.sh generate
13
+ # These are intentionally committed so consumers can train without regenerating.
14
+
15
+ # Model artifacts
16
+ pluckit-lora/
17
+ *.gguf
@@ -0,0 +1,86 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based
4
+ on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0a1] — 2026-04-10
10
+
11
+ First public alpha. Query, view, and mutate all work end-to-end.
12
+
13
+ ### Added
14
+
15
+ - **`Plucker` — a fluent entry point** that wraps a DuckDB connection, loads
16
+ the `sitting_duck` community extension, and exposes `find()`, `view()`, and
17
+ mutation methods on lazy `Selection` objects. Selections are DuckDB
18
+ relations that chain filters, navigation, and terminal operations without
19
+ materializing until necessary.
20
+
21
+ - **CSS-like selector language** (`.fn`, `.cls`, `.call`, `.fn#name`,
22
+ `.fn:exported`, `.fn[name^=test_]`, `.cls#Foo .fn`, `.fn:has(.call#x)`)
23
+ compiled to SQL WHERE fragments over sitting_duck's `read_ast()` table.
24
+ Supports 27 languages via tree-sitter.
25
+
26
+ - **`AstViewer` plugin** — a CSS-stylesheet-style declaration language
27
+ (`{ show: signature; }`, `{ show: outline; }`, `{ show: 10; }`, etc.)
28
+ attached to selectors. Synthesized signatures from sitting_duck's native
29
+ extraction columns. Multi-match signature queries collapse to a markdown
30
+ table automatically.
31
+
32
+ - **Mutation engine with transactional rollback.** Line-granularity splicing
33
+ with per-file snapshots, reverse-order application (later edits don't
34
+ shift earlier line numbers), and re-parse validation. Any syntax error
35
+ rolls back every affected file.
36
+
37
+ - **Mutation vocabulary:** `ReplaceWith`, `ScopedReplace`, `Prepend`,
38
+ `Append`, `Wrap`, `Unwrap`, `Remove`, `Rename`, `AddParam`, `RemoveParam`,
39
+ `AddArg`, `RemoveArg`, `ClearBody`, `InsertBefore`, `InsertAfter`.
40
+ `InsertBefore`/`InsertAfter` take a CSS selector as the anchor and resolve
41
+ it via a scoped AST sub-query — no heuristics.
42
+
43
+ - **`pluckit` CLI** with four subcommands:
44
+ - `init` — install and verify the required DuckDB community extensions.
45
+ - `view` — render matched code regions as markdown, reading queries from
46
+ argv, a file, or stdin.
47
+ - `find` — list matches for scripting. Four output formats: `locations`
48
+ (file:line:name, default), `names`, `signature` (markdown table), `json`.
49
+ - `edit` — apply structural mutations. Chainable within one invocation:
50
+ multiple operations per group, multiple groups separated by `--`,
51
+ with a real unified-diff preview in `--dry-run`.
52
+
53
+ - **Plugin system** — third-party plugins register new methods on
54
+ `Selection`, new pseudo-classes for the selector compiler, and optional
55
+ upgrades to existing methods (e.g., the `Calls` plugin will upgrade
56
+ `callers()` with import-resolved results).
57
+
58
+ - **Cross-language indent detection** for mutations in Python, C++, Go,
59
+ Java, TypeScript, and Rust. Body-frame indent is computed from file
60
+ context, not hard-coded to 4 spaces.
61
+
62
+ - **CI scaffolding** — GitHub Actions workflow runs lint, pytest, and a
63
+ wheel build on every push and PR.
64
+
65
+ ### Infrastructure
66
+
67
+ - MIT licensed.
68
+ - PyPI distribution name: `ast-pluckit`. Import name, CLI name, and repo
69
+ name are all `pluckit`. (The bare `pluckit` PyPI name is held by an
70
+ abandoned 2019 project.)
71
+ - Python 3.10+ supported.
72
+ - Documentation published at [pluckit.readthedocs.io](https://pluckit.readthedocs.io).
73
+
74
+ ### Known limitations
75
+
76
+ - Call graph, git history, and scope plugins are stubs — landing in v0.2.
77
+ - pluckit's selector compiler supports only a subset of sitting_duck's full
78
+ selector language. Richer features like `:calls()`, `:matches()`, and
79
+ `:scope()` work when calling `ast_select` directly against the underlying
80
+ DuckDB connection.
81
+ - Mutations operate at line granularity because sitting_duck's `read_ast`
82
+ does not yet expose byte offsets. Character-level insertions
83
+ (`--insert-chars`) are reserved for v0.2.
84
+
85
+ [Unreleased]: https://github.com/teaguesterling/pluckit/compare/v0.1.0a1...HEAD
86
+ [0.1.0a1]: https://github.com/teaguesterling/pluckit/releases/tag/v0.1.0a1
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Teague Sterling
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,376 @@
1
+ Metadata-Version: 2.4
2
+ Name: ast-pluckit
3
+ Version: 0.1.0a1
4
+ Summary: A fluent API for querying, viewing, and mutating source code — CSS selectors over ASTs, backed by DuckDB.
5
+ Project-URL: Homepage, https://github.com/teaguesterling/pluckit
6
+ Project-URL: Documentation, https://pluckit.readthedocs.io
7
+ Project-URL: Repository, https://github.com/teaguesterling/pluckit
8
+ Project-URL: Issues, https://github.com/teaguesterling/pluckit/issues
9
+ Project-URL: Changelog, https://github.com/teaguesterling/pluckit/blob/main/CHANGELOG.md
10
+ Author-email: Teague Sterling <teaguesterling@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: ast,code-search,codemod,css-selectors,duckdb,refactoring,sitting-duck,static-analysis,tree-sitter
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Software Development :: Code Generators
26
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
+ Classifier: Topic :: Software Development :: Quality Assurance
28
+ Classifier: Topic :: Utilities
29
+ Classifier: Typing :: Typed
30
+ Requires-Python: >=3.10
31
+ Requires-Dist: duckdb>=1.1.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=8.0; extra == 'dev'
34
+ Requires-Dist: ruff>=0.6; extra == 'dev'
35
+ Provides-Extra: docs
36
+ Requires-Dist: mkdocs-exclude>=1.0; extra == 'docs'
37
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
38
+ Requires-Dist: mkdocs>=1.6; extra == 'docs'
39
+ Requires-Dist: pymdown-extensions>=10.9; extra == 'docs'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # pluckit
43
+
44
+ *A fluent API for querying, viewing, and mutating source code. CSS selectors over ASTs, backed by DuckDB.*
45
+
46
+ pluckit lets you select code with CSS-like selectors, render it as formatted
47
+ source regions, and apply structural mutations — all from a single fluent API
48
+ or a compact CLI. Under the hood it's a thin Python layer over DuckDB with
49
+ the [sitting_duck](https://github.com/teaguesterling/sitting_duck) AST
50
+ extension, so queries compile to SQL and run against tree-sitter ASTs for
51
+ 27 languages.
52
+
53
+ - **Documentation:** [pluckit.readthedocs.io](https://pluckit.readthedocs.io)
54
+ - **PyPI:** [`ast-pluckit`](https://pypi.org/project/ast-pluckit/)
55
+ - **Changelog:** [CHANGELOG.md](CHANGELOG.md)
56
+
57
+ > **Status:** v0.1-alpha. Query, view, and mutate work end-to-end. Call graph,
58
+ > history, and scope plugins are v0.2.
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ pip install ast-pluckit
64
+ ```
65
+
66
+ The distribution name is `ast-pluckit` (the bare `pluckit` name was taken on
67
+ PyPI by an abandoned 2019 project). The import name and CLI are still
68
+ `pluckit`:
69
+
70
+ ```bash
71
+ pluckit view ".fn#main" src/**/*.py
72
+ ```
73
+ ```python
74
+ from pluckit import Plucker
75
+ ```
76
+
77
+ For local development:
78
+
79
+ ```bash
80
+ pip install -e .
81
+ ```
82
+
83
+ pluckit needs DuckDB with the `sitting_duck` community extension. It will
84
+ auto-install on first use, but you can also run:
85
+
86
+ ```bash
87
+ pluckit init
88
+ ```
89
+
90
+ to install and verify the extensions eagerly and get clearer diagnostics
91
+ if anything fails.
92
+
93
+ ## The CLI
94
+
95
+ Full reference lives in the [CLI docs](https://pluckit.readthedocs.io/en/latest/cli/).
96
+ The short version:
97
+
98
+ ### View — render matched code regions as markdown
99
+
100
+ ```bash
101
+ # Show the full body of a function
102
+ pluckit view ".fn#parse_chain" training/chain_parser.py
103
+
104
+ # Just the signature
105
+ pluckit view ".fn#parse_chain { show: signature; }" training/chain_parser.py
106
+
107
+ # First 10 lines
108
+ pluckit view ".fn#parse_chain { show: 10; }" training/chain_parser.py
109
+
110
+ # Class outline — header + method signatures
111
+ pluckit view ".cls#ChainSampler" training/chain_sampler.py
112
+
113
+ # Multi-rule query (CSS-stylesheet style)
114
+ pluckit view ".fn { show: signature; } #main { show: body; }" training/generate.py
115
+
116
+ # Read the query from stdin
117
+ echo ".fn[name^=test_] { show: signature; }" | pluckit view - tests/*.py
118
+
119
+ # Or from a file
120
+ pluckit view --query-file audit.q src/**/*.py
121
+
122
+ # Multiple paths and glob patterns
123
+ pluckit view ".fn#parse" src/*.py lib/*.py
124
+ ```
125
+
126
+ When a signature-mode query matches more than one function, the output
127
+ collapses into a markdown table — dramatically smaller than a code fence per
128
+ match:
129
+
130
+ ```
131
+ | File | Lines | Signature |
132
+ |---|---|---|
133
+ | src/validate.py | 35-54 | `def _is_garbled(intent: str) -> bool:` |
134
+ | src/validate.py | 73-88 | `def _flatten_ops(comp: Any) -> list[str]:`|
135
+ | src/validate.py | 102-196 | `def validate_chain(chain: str) -> Result:`|
136
+ ```
137
+
138
+ ### Find — list matches for scripting
139
+
140
+ `find` is the terse companion to `view`. It emits one line per match in
141
+ formats designed for shell pipelines and agent tool-use:
142
+
143
+ ```bash
144
+ # Default: file:line:name, one per line — feed directly into $(...)
145
+ pluckit find ".fn:exported" src/**/*.py
146
+
147
+ # Just the names — good for set operations
148
+ pluckit find ".fn[name^=test_]" --format names tests/*.py | sort -u
149
+
150
+ # Signature table — a lightweight audit view
151
+ pluckit find ".fn:exported" --format signature src/**/*.py
152
+
153
+ # Machine-readable JSON — one object per match
154
+ pluckit find ".cls" --format json src/**/*.py
155
+
156
+ # Just the total count
157
+ pluckit find ".fn" --count src/**/*.py
158
+ ```
159
+
160
+ ### Edit — apply structural changes to matched nodes
161
+
162
+ All edits are **transactional**: if any affected file fails syntax re-validation
163
+ after splicing, every file is rolled back to its pre-edit state. Use `--dry-run`
164
+ to see how many matches each path would affect before writing.
165
+
166
+ ```bash
167
+ # Replace a function's body entirely
168
+ pluckit edit ".fn#foo" --replace-with "def foo():\n return 1" src/*.py
169
+
170
+ # Scoped find-and-replace within matched nodes (2-arg replace)
171
+ pluckit edit ".fn#validate" --replace "return None" "raise ValueError()" src/*.py
172
+
173
+ # Add a parameter to every matched function
174
+ pluckit edit ".fn:exported" --add-param "timeout: int = 30" src/**/*.py
175
+
176
+ # Remove a parameter by name
177
+ pluckit edit ".fn#fetch_user" --remove-param "cache" src/*.py
178
+
179
+ # Add a keyword argument at every call site (paired with --add-param above)
180
+ pluckit edit ".call#fetch_user" --add-arg "timeout=timeout" src/**/*.py
181
+
182
+ # Remove a keyword argument from every call site
183
+ pluckit edit ".call#fetch_user" --remove-arg "cache" src/**/*.py
184
+
185
+ # Remove matched nodes entirely
186
+ pluckit edit ".fn#deprecated_helper" --remove src/*.py
187
+
188
+ # Clear a function/class body to `pass` (Python) or `{}` (C-family)
189
+ pluckit edit ".fn#todo_later" --clear-body src/*.py
190
+
191
+ # Rename a definition (first name occurrence)
192
+ pluckit edit ".fn#old_name" --rename "new_name" src/*.py
193
+
194
+ # Insert lines at the top/bottom of matched function bodies
195
+ pluckit edit ".fn:exported" --prepend-lines "logger.debug('entered')" src/*.py
196
+ pluckit edit ".fn:exported" --append-lines "logger.debug('exited')" src/*.py
197
+
198
+ # Insert at a specific sibling position — anchor is a CSS selector
199
+ # resolved against each matched node's subtree (exact, not heuristic)
200
+ pluckit edit ".cls#Foo" --insert-lines before ".fn#bar" "def pre_bar(self): pass" src/*.py
201
+ pluckit edit ".cls#Foo" --insert-lines after ".fn#bar" "def post_bar(self): pass" src/*.py
202
+ pluckit edit ".fn#main" --insert-lines before ".ret" "cleanup()" src/*.py
203
+
204
+ # Wrap matched nodes
205
+ pluckit edit ".call#query" --wrap "try:" "except DatabaseError:\n raise" src/*.py
206
+
207
+ # See what would change without writing — prints a real unified diff
208
+ pluckit edit ".fn#foo" --remove --dry-run src/*.py
209
+ ```
210
+
211
+ **Chaining edits.** Multiple operations can share a single group, and
212
+ multiple `(selector, operations)` groups can run in one invocation,
213
+ separated by `--`. This is how you keep an API and its call sites in
214
+ sync atomically:
215
+
216
+ ```bash
217
+ pluckit edit \
218
+ ".cls#Foo .fn#__init__" --add-param "foo: int = 30" \
219
+ --append-lines "self.foo = foo" \
220
+ -- \
221
+ ".call#Foo" --add-arg "foo=10" \
222
+ src/**/*.py
223
+ ```
224
+
225
+ The whole command is one transaction: if any group produces invalid
226
+ syntax in any file, every affected file rolls back to its pre-edit state.
227
+
228
+ **Line-level vs character-level edits.** `--prepend-lines`, `--append-lines`,
229
+ and `--insert-lines` all insert whole new lines at indentation-matched
230
+ positions. The 2-arg `--replace OLD NEW` is the character-level equivalent —
231
+ it does a string-level replace within the matched node's text, preserving
232
+ the rest of each line verbatim. `--replace-with` replaces the entire node.
233
+ Character-level `--insert-chars` for inline positional insertions is
234
+ reserved for v0.2.
235
+
236
+ ## The Python API
237
+
238
+ ```python
239
+ from pluckit import Plucker, AstViewer
240
+
241
+ pluck = Plucker(code="src/**/*.py", plugins=[AstViewer])
242
+
243
+ # Query
244
+ fns = pluck.find(".fn:exported")
245
+ print(fns.count()) # 47
246
+ print(fns.names()[:5]) # ['authenticate', 'decode_jwt', ...]
247
+
248
+ # View
249
+ print(pluck.view(".fn#validate_token { show: signature; }"))
250
+
251
+ # Mutate (v0.1)
252
+ pluck.find(".fn#validate_token").replaceWith(
253
+ "return None",
254
+ "raise ValueError('token required')",
255
+ )
256
+ pluck.find(".fn:exported").addParam("timeout: int = 30")
257
+ ```
258
+
259
+ ### Module-level shortcuts
260
+
261
+ ```python
262
+ from pluckit import view
263
+
264
+ # One-shot viewer query — creates an ephemeral Plucker
265
+ print(view(".fn#main { show: outline; }", code="src/**/*.py"))
266
+ ```
267
+
268
+ ## Selector syntax
269
+
270
+ Selectors mirror CSS but address AST nodes:
271
+
272
+ | Syntax | Meaning |
273
+ |---------------------------------|---------------------------------------------------|
274
+ | `.fn` | All function definitions (cross-language alias) |
275
+ | `.cls`, `.class` | All class definitions |
276
+ | `.call` | All call expressions |
277
+ | `.fn#name` | Function named `name` |
278
+ | `.fn:exported` | Public (non-underscore) functions |
279
+ | `.fn[name^=test_]` | Functions whose name starts with `test_` |
280
+ | `.fn[name*=auth]` | Functions whose name contains `auth` |
281
+ | `.cls#Foo .fn` | Functions inside `class Foo` |
282
+ | `.fn:has(.call#execute)` | Functions that call `execute()` |
283
+ | `.fn:not(:has(.try))` | Functions with no try block |
284
+
285
+ sitting_duck's full selector language is richer than what pluckit currently
286
+ compiles — see its docs for `:calls()`, `:matches()`, `:scope()`, and the
287
+ call graph pseudo-elements. These work when you call `ast_select` directly
288
+ against the underlying DuckDB connection; pluckit's fluent layer supports
289
+ a growing subset.
290
+
291
+ ## Viewer `show` modes
292
+
293
+ The viewer supports a small declaration language — CSS declaration blocks
294
+ attached to selectors:
295
+
296
+ | Show value | Behavior |
297
+ |--------------|-------------------------------------------------------------------|
298
+ | `body` | Full matched node text (default for functions, calls, statements) |
299
+ | `signature` | Declaration line only (synthesized from native AST metadata) |
300
+ | `outline` | Class header + method signatures + dataclass fields (default for classes) |
301
+ | `enclosing` | Walk up to the nearest scope and render *that* as body |
302
+ | `N` (number) | First N lines of the body with `...` truncation marker |
303
+
304
+ Rules compose like a stylesheet:
305
+
306
+ ```css
307
+ .fn { show: signature; } /* default most functions to signature */
308
+ .fn#main { show: body; } /* except main — show its full body */
309
+ .cls#Config { show: outline; } /* Config class with methods listed */
310
+ ```
311
+
312
+ ## Plugins
313
+
314
+ pluckit is composable. Core capabilities stay in `Selection`; anything that
315
+ depends on extra data or infrastructure moves to a plugin:
316
+
317
+ ```python
318
+ from pluckit import Plucker, AstViewer
319
+
320
+ pluck = Plucker(
321
+ code="src/**/*.py",
322
+ plugins=[AstViewer], # viewer with `show:` declarations
323
+ # plugins=[Calls], # call graph (v0.2)
324
+ # plugins=[History], # git history (v0.2)
325
+ # plugins=[Scope], # scope analysis (v0.2)
326
+ )
327
+ ```
328
+
329
+ Plugins register new methods on `Selection`, new pseudo-classes for the
330
+ selector compiler, and optional upgrades for existing methods (e.g.,
331
+ fledgling-python can upgrade `callers()` with import-resolved results).
332
+
333
+ ## Training data
334
+
335
+ The `training/` directory contains a synthetic training data generator that
336
+ produces (intent, chain) pairs for fine-tuning a small code model. It works
337
+ entirely from the API spec in `reference/api.yaml` — no pluckit runtime
338
+ needed. A ~40k-pair corpus (19% error-driven, 19% context-bearing) is
339
+ committed via git LFS under `training/`.
340
+
341
+ See `training/README.md` for generation and formatting.
342
+
343
+ ## Architecture
344
+
345
+ ```
346
+ pluckit.Plucker entry point, plugin registry, DuckDB context
347
+
348
+ pluckit.Selection lazy DuckDB relation chain
349
+ │ query, filter, navigate, read, mutate
350
+ ├── pluckit._sql selector → SQL WHERE fragments
351
+
352
+ pluckit.plugins optional capabilities
353
+ ├── AstViewer CSS-style viewer with `show:` declarations
354
+ ├── Calls (v0.2) call graph via name-join + plugin upgrades
355
+ ├── History (v0.2) git history via duck_tails
356
+ └── Scope (v0.2) read/write interface via flags byte
357
+
358
+ pluckit.mutation byte-splice engine with transaction rollback
359
+ └── pluckit.mutations ReplaceWith, AddParam, Wrap, Rename, ...
360
+ ```
361
+
362
+ All queries ultimately compile to SQL over sitting_duck's `read_ast()` table.
363
+ Mutations read files, apply string-level splices at line granularity, re-parse
364
+ to validate, and roll back on any syntax error.
365
+
366
+ ## Contributing
367
+
368
+ Run tests:
369
+
370
+ ```bash
371
+ pip install -e ".[dev]"
372
+ pytest
373
+ ```
374
+
375
+ 189 tests covering selectors, the Selection API, the plugin system, the
376
+ viewer, the CLI, and the mutation engine.