Skip to content

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 where filter(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 — today Selection.isolate() extracts only the first match. Multi-match would fan out, producing a list of Isolated objects (one per match). Not complicated; waiting on a concrete need.

  • blame() in the History pluckin — raises PluckerError today. Blocked on duck_tails shipping a git_blame table 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 on read_ast rows.

  • Cache benchmarksPlucker(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 the query → transform → diff → review → apply loop. Tracked as pluckit#4.

  • @file argument syntax — let mutation args (and any string chain argument) read content from a file with an @path prefix: replaceWith @patches/new_handler.py instead of inlining escaped multi-line code. Applies uniformly to from_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.