Roadmap¶
Tracked items that don't fit cleanly into any one release's changelog. Rough grouping by where the friction is: near-term (we could land it next), architectural (bigger shape, needs design), and upstream (waiting on sitting_duck or duck_tails).
Near-term¶
These are scoped well enough that they could land in the next release or two when someone picks them up.
-
Chain.sweep(param, values)— parameter-sweep batch, the rare case wherefilter(name__in=[...])doesn't cover. Runs the same chain N times with different input values, aggregates results. Covered by the existing selection-stack abstraction; just needs an op and a small evaluator extension. -
Multi-match
Isolated— todaySelection.isolate()extracts only the first match. Multi-match would fan out, producing a list ofIsolatedobjects (one per match). Not complicated; waiting on a concrete need. -
blame()in theHistorypluckin — raisesPluckerErrortoday. Blocked on duck_tails shipping agit_blametable function (issue #18 upstream). -
Character-level edits (
--insert-chars) — line-level splicing is what we have; inline positional insertions are reserved for a future milestone. Pluckit-side work is modest once sitting_duck exposes byte offsets onread_astrows. -
Cache benchmarks —
Plucker(cache=True)should be meaningfully faster on repeat queries. We've never measured it against a realistic codebase. One afternoon of work and a blog-ready chart. -
Selection.patch()+diff()terminal — complete the mutation vocabulary by supporting unified-diff input (the natural format for code review workflows).patch()applies a diff to matched regions;diff()outputs a transform as a unified diff instead of applying it. Enables thequery → transform → diff → review → applyloop. Tracked as pluckit#4. -
@fileargument syntax — let mutation args (and any string chain argument) read content from a file with an@pathprefix:replaceWith @patches/new_handler.pyinstead of inlining escaped multi-line code. Applies uniformly tofrom_argv,from_json, and mutation methods. Tracked as pluckit#5.
Architectural¶
Bigger shape questions. Landing any of these requires design work before implementation.
Dynamic, context-aware tool loading (via kibitzer modes)¶
Today, loading pluckit as an MCP tool (via squackit or similar) exposes the full tool surface whether or not the current conversation needs it. A conversation about documentation loads source-mutation tools it will never use; a code-review conversation loads a viewer that wastes context budget.
Upstream mechanism: kibitzer mode-gated tool visibility
(kibitzer#1).
Kibitzer already defines modes (explore / implement / test /
docs / review) that gate writable paths. The proposal is to extend
them to gate visible tools per mode, filtering at MCP capability-
negotiation time. Per-turn attention surface drops from ~100 tools to
5-10 per mode; total capability is unchanged. ChangeToolMode is the
existing escape hatch.
Integration shape: a shared participant API that any tool can implement. Not a per-consumer custom method, but a single protocol that pluckit + lackpy + blq + jetsam + any future contributor all implement the same way. Kibitzer owns the protocol definition; tool authors just import it.
Bidirectional: tools reflect back to kibitzer on mode change.
The naive approach — a static intent_tags attribute set once at
plugin registration — is too rigid. A tool's offering can reshape by
mode: in explore it offers read-only inspection methods; in
implement it adds mutation methods.
Intent-driven guidance. When the agent has a specific task in
mind (intent), participants can return usage guidance alongside
the raw tool list — "here's how to use me for this kind of work".
This turns the participant API from passive metadata into an active
teacher: the agent asks "I want to refactor this function's call
sites", kibitzer broadcasts the intent to every participant, and the
tools themselves pitch in with concrete usage patterns. Example:
# Protocol defined in kibitzer, implemented by any tool
class KibitzerParticipant(Protocol):
def on_mode_change(
self,
mode: str,
intent: str | None = None,
) -> ParticipantResponse:
"""Return the tools and (optional) usage guidance this
participant offers for `mode` and — if supplied — `intent`.
Called by kibitzer on every ChangeToolMode transition and
optionally on intent changes within a mode.
"""
@dataclass
class ParticipantResponse:
tools: list[ToolOffering]
guidance: list[str] = field(default_factory=list) # prose hints
examples: list[dict] = field(default_factory=list) # optional structured examples
class Calls(Pluckin): # pluckin side
def on_mode_change(self, mode, intent=None) -> ParticipantResponse:
if mode not in ("explore", "review"):
return ParticipantResponse(tools=[])
tools = [
ToolOffering(name="callers", ...),
ToolOffering(name="callees", ...),
ToolOffering(name="references", ...),
]
guidance = []
if intent and "refactor" in intent.lower():
guidance.append(
"Before renaming a function, use `callers` to find "
"every call site. Chain it: "
"find .fn#old callers rename new — this renames both "
"the definition and every call in one transaction."
)
elif intent and ("security" in intent.lower() or "audit" in intent.lower()):
guidance.append(
"Use `references` on auth-related functions to find "
"all code paths that touch them. `callers` misses name "
"references that aren't direct calls (e.g., function "
"objects passed to higher-order functions)."
)
return ParticipantResponse(tools=tools, guidance=guidance)
On a mode transition (or intent change), kibitzer walks every
registered participant, calls on_mode_change, aggregates the
returned tool offerings into the new MCP capability set, and surfaces
the guidance strings to the agent alongside the tool list. Agents
get "here are the tools visible in this mode" + "here's how the tools
themselves suggest using them for what you said you wanted to do."
Participants without on_mode_change fall back to "always visible,
no guidance" — no regression for existing pluckins.
Why the guidance is bundled with the tool offering: the tool
knows its own nuances better than the agent does. A human writing a
runbook discovers Calls.callers misses function-object references
the hard way; a pluckin that carries that guidance teaches every
agent that invokes it in a security-review intent. The cost of
discovery transfers from the agent to the pluckin author, once.
The primitive generalizes: this same participant API can serve LSP-aware filtering, VSCode command palettes, etc. Each consumer defines its own protocol (sharing the bidirectional-on-change shape); pluckins implement whichever ones they care about.
Tool search / recommendation via kibitzer¶
A structured tool surface (PluckinRegistry.pluckins is the first
piece) also enables searchtools / recommend_tools primitives in
kibitzer. An agent asks "what pluckit tools would help me answer
this?" and gets a ranked list back, with filters on intent / context
/ cost.
This complements the mode-gated loading above. Mode-gating is the
coarse filter ("which tools does this conversation's mode even see");
recommendation is the fine filter ("of those visible tools, which is
the right one for this specific sub-task"). kibitzer#1 notes the gap
it leaves: once visibility is constrained, a ToolSearch-equivalent
is needed for cross-mode discovery. That's the recommendation
primitive.
Consumer-agnostic tool contracts¶
The Plugin → Pluckin rename (v0.9.0) and the PluckinRegistry.pluckins
iterator (also v0.9.0) were the first steps toward decoupling pluckit
from any specific consumer (squackit, LSP, VSCode extensions, etc.).
The open architectural question: what does a pluckin expose to enable
consumer-specific presentation without coupling to any specific
consumer?
Current working idea (discussed but not implemented): plugins declare
optional methods like squackit_tools(), lsp_actions(),
vscode_commands(). Consumers sniff for their specific method and
fail gracefully when absent. Pluckit knows nothing about any specific
consumer.
MCP-side pagination/batch integration¶
v0.11.0 landed chain-level pagination. The open question: does the consumer-side (squackit/etc.) need any scaffolding, or is it already sufficient to forward the result envelope? First real agent workflow using paginated pluckit chains will surface this.
Upstream-blocked¶
Work that's architecturally ready on the pluckit side and just needs sitting_duck or duck_tails to ship a matching feature.
Calls pluckin simplification¶
When sitting_duck ships its scope struct ({current, function,
class, module, stack}) on every read_ast row, the Calls pluckin's
provenance-walk + per-file ast_select fan-out collapses to a single
lateral join. See src/pluckit/pluckins/calls.py module docstring.
duck_tails.git_read named repo_path parameter¶
git_read accepts repo_path as a named input but resolves relative
git:// URIs against the process cwd instead of the repo at
repo_path. Workaround lives in src/pluckit/pluckins/history.py
(embed absolute path in the URI). Tracked as
duck_tails#17.
Native ast_select table-source support¶
ast_select takes a file path (goes to read_ast). For the AST cache
to use it against a cached table, we'd want ast_select to also
accept a table name. Today, ASTCache queries cached tables with
pluckit's own _selector_to_where compiler instead. Not blocking,
just a polish.
Not on the roadmap (things we deliberately aren't doing)¶
- A pluckit LSP server. pluckit's job is the query/mutate surface; LSP integration is a consumer's job (could be a squackit feature or its own package). We might expose LSP-friendly hooks on pluckins, but pluckit itself stays MCP/CLI-first.
- A web UI / dashboard. Same reasoning — consumer's job.
- Generic full-text search over ASTs. sitting_duck's selectors
plus pluckit's
filter(name__contains=...)already cover this. No need for a separate index.
How to propose a roadmap item¶
Open an issue with the label roadmap. Include:
- Motivation — concrete use case (not "it would be nice")
- Proposed shape — rough API sketch or sequence diagram
- Upstream dependencies — sitting_duck / duck_tails features it requires
- Non-goals — what this explicitly doesn't cover
If it's small enough to land in a release, it'll migrate from this doc to a milestone.