Structured completion pipeline with semantic metadata#2007
Draft
curtisman wants to merge 53 commits intomicrosoft:mainfrom
Draft
Structured completion pipeline with semantic metadata#2007curtisman wants to merge 53 commits intomicrosoft:mainfrom
curtisman wants to merge 53 commits intomicrosoft:mainfrom
Conversation
…tics, and tests - Refactor matchGrammarCompletion to use candidate-based approach with post-loop filtering by maxPrefixLength so exact matches dominate shorter partial completions. - Category 1 (exact match) now updates maxPrefixLength. - Remove isPartialPrefixOfStringPart; Category 3b reports all string parts unconditionally (caller filters by trailing text). - Fix needsSeparator to strictly describe the boundary at matchedPrefixLength, independent of any trailing user-typed content. - Simplify Category 3a wildcard needsSep guard to match Categories 2/3b. - Add grammarCompletionLongestMatch.spec.ts (43 tests). - Add grammarCompletionCategory3bLimitation.spec.ts (23 tests including 7 needsSeparator tests for Category 3b). - Update grammarCompletionPrefixLength.spec.ts for new behavior.
Command completion now sets needsSeparator on subcommand groups and backs startIndex past trailing whitespace, matching the grammar matcher's contract. This makes '@config ' (and similar inputs) show the completion menu through the generic path. - Add needsSeparator to Subcommands completion groups in completion.ts - Back up startIndex past whitespace when needsSeparator is set - Guard parameter startIndex with hasSubcommandCompletions flag - Change subcommand inclusion condition from suffix.length===0 to - Remove @-command special case from partialCompletionSession.ts - Update and expand tests for both backend and frontend
Allow empty input to fetch completions so clicking an empty input box shows the completion menu (e.g. '@'). When the user types text that exactly matches one trie entry and is not a prefix of any other, trigger a re-fetch to get the next level of completions. - Remove empty-input guard in PartialCompletionSession.update() - Change ISearchMenu.updatePrefix to return boolean (true = uniquely satisfied) - SearchMenu.updatePrefix returns true when exactly one trie match equals the prefix - reuseSession() uses the return value to trigger re-fetch - Update and expand tests for empty input and unique satisfaction
- Clarify needsSeparator comment in CompletionGroup and CommandCompletionResult to describe the grammar relationship rather than implying the user hasn't typed a separator yet - Add 'pnpm run clean' to the CLAUDE.md build instructions - Fix minor whitespace alignment in CLAUDE.md
Completion pipeline (dispatcher): - Add complete field to CommandCompletionResult for exhaustive sets - Eliminate undefined returns from getCommandCompletion (always return result) - Fix @com partial agent completion (suffix is filter text, not a gate) - Fix subcommand/startIndex: compute parameters first, conditionally add subcommands - Merge flat descriptor path into unified three-way if/else if/else - Add contract comments on getCommandCompletion, getCommandParameterCompletion, ParameterCompletionResult, and resolveCommand - 28 dispatcher completion tests (up from ~10) Shell (partialCompletionSession): - Fix uniquelySatisfied: always re-fetch for next-level completions regardless of complete flag (complete describes THIS level, not next) - Fix no-match fallthrough: re-fetch when complete=false and trie has zero matches (non-exhaustive set may have unlisted completions) - Update state machine documentation with four decision types - 43 shell tests (up from ~35)
Move the Agent Names completion group out of the else-if (table !== undefined) branch into an independent check after the three-way if/else if/else. The condition (parsedAppAgentName === undefined && commands.length === 0) is purely about resolution state, not about what the fallback agent provides. This ensures agent names are offered whenever the user hasn't typed a recognized agent, regardless of whether the fallback agent has a command table, a flat descriptor, or no commands at all. Tests: verify Agent Names presence/absence for @, @CompTest, @nocmdtest.
- Add ParseParamsResult type extending ParsedCommandParams with remainderLength to report unconsumed parameter text - Track parser consumption position (lastGoodCurrLength) through flag name, flag+value, and argument parsing stages - Rewrite startIndex computation in getCommandParameterCompletion to use remainderLength instead of reverse-engineering from token lengths - Handle pending non-boolean flags correctly: consumed flag name keeps startIndex at full input length - Add tests for non-boolean flag without trailing space and unrecognized flag prefix as filter text
Replace the boolean needsSeparator flag with a 4-value SeparatorMode enum across all completion types. The new type distinguishes: - "space": whitespace required (command subcommands) - "spacePunctuation": whitespace or punctuation (grammar boundaries) - "optional": separator accepted but not required (CJK, after @) - "none": no separator ([spacing=none] grammars) Default is "space" when omitted, so agents that forget to set the field get safe behavior (menu defers until whitespace is typed). Packages updated: - agentSdk: SeparatorMode type, CompletionGroup.separatorMode field - dispatcher/types: CommandCompletionResult.separatorMode - actionGrammar: GrammarSeparatorMode subset type, candidate/merge helpers - cache: CompletionResult.separatorMode, mergeSeparatorModes helper - dispatcher: aggregateSeparatorMode, @-level uses "optional" - shell: mode-aware separator detection with per-mode regex - cli: updated separator check - All corresponding tests updated
- Add remainder.spec.ts with 30 tests covering remainderLength in parseParams for non-partial, partial-consumed, and partial-unconsumed cases. - In completion.ts, universally back startIndex over inter-token whitespace between the last consumed token and the unconsumed remainder, replacing the old rewind-for-filter-text logic. - Update ParseParamsResult comments in parameters.ts to clarify that remainderLength excludes inter-token whitespace. - Add completion tests for whitespace backing with remainderLength > 0 (single and multiple spaces). - Update existing completion test expectations for the new startIndex behavior.
…e backing unconditional - Make lastCompletableParam path exclusive: clear other completions and adjust startIndex to token start when inside an open-quoted or implicitQuotes token - Make outer whitespace backing unconditional so the anchor always sits at the last token boundary (consumers default separatorMode to "space" and expect the separator in rawPrefix) - Remove inner whitespace backing that broke flag filtering - Update contract and inline comments to describe the general consumer expectation - Add twoarg and search test commands; add 5 new lastCompletableParam tests; update expectations for unconditional backing
Grammar-reported prefixLength is relative to the token content start (after the separator space), not to tokenBoundary (before it). Track tokenStartIndex separately so Site 4 adds prefixLength to the correct base. Refactor grammar/grammariq test mocks: - Extract shared grammarCompletion() helper with CJK and English branches - Return suffix completions (タワー/駅, Tower/Station) with realistic prefixLength and separatorMode instead of full-word completions - Validate names parameter in all mock getCompletion implementations Add 3 new tests (44→47): English prefix with space separator, completed CJK match returns no completions, no-text initial completions via grammariq.
Replace post-loop filtering of completion and property candidates with eager inline filtering during the main loop. Candidates are added directly to the output arrays, and whenever maxPrefixLength increases the arrays are cleared — eliminating the need for intermediate candidate arrays and a separate post-loop filtering pass.
…-wins in grammarStore
Empty completions now flow through the SHOW path naturally: - complete=true (fully specified) → reuse (menu inactive, complete flag wins) - complete=false (error path) → re-fetch when user types past anchor Set separatorMode='none' on empty results so the separator check doesn't interfere with the SHOW path decision.
Extract remainderIndex to avoid redundant computation and initialize tokenStartIndex unconditionally so the groupPrefixLength offset calculation no longer needs a nullish coalescing fallback. Add tokenStartIndex to the debug log for easier troubleshooting.
Move completion text construction, separator determination, and debug logging inside the maxPrefixLength guard in all three completion paths (string part, wildcard property, current string part). This avoids unnecessary work for candidates that will be discarded.
After handleSelect replaced text via Range.insertNode() + collapse(), the selection's endContainer pointed at the parent element rather than the deepest text node. This caused isSelectionAtEnd() to fail, so the selectionchange-triggered update bailed out without fetching new completions. Fix: normalize the text entry to merge adjacent text nodes, place the cursor at the end of the last text node (matching isSelectionAtEnd's expectation), and explicitly call update() instead of relying solely on the async selectionchange event.
… new CompletionGroups wrapper type These two fields are always identical across all CompletionGroup objects in a single completion response. Introduce a CompletionGroups wrapper type that holds the shared prefixLength and separatorMode alongside the array of groups, eliminating per-group duplication. Updated across the entire monorepo: - agentSdk: new CompletionGroups type, updated AppAgentCommandInterface - agentRpc: updated RPC invoke/server types - dispatcher: completion engine, command handlers, config handlers - shell: ShellSetSettingCommandHandler - tests: completion.spec.ts
…ernal ParseParamsResult The tokens, lastCompletableParam, lastParamImplicitQuotes, and nextArgs fields on ParsedCommandParams were only used internally by the dispatcher's command completion engine. Move them into the existing dispatcher-internal ParseParamsResult type, keeping the public SDK surface to just args and flags. - Remove four completion fields from agentSdk ParsedCommandParams - Add them to dispatcher's ParseParamsResult alongside remainderLength - Update completion.ts getPendingFlag to use ParseParamsResult type - Simplify browser agent's manually-constructed ParsedCommandParams - Refactor MCP agent provider to extract values from params.args instead of the removed params.tokens
tryPartialStringMatch now handles single-word parts (returns the word directly instead of undefined), so Category 2 and Category 3b both use the helper consistently with no fallback logic.
Rename the 'complete' property to 'closedSet' in all type definitions, producers, consumers, and tests. The new name better captures the semantics: closedSet=true means 'if the user types something not in this list, no further completions can exist beyond it', without implying that all possible values are present. Affected types: GrammarCompletionResult, CompletionResult, CompletionGroups, CommandCompletionResult, ParameterCompletionResult. Files changed: 8 source files, 4 test files.
- Place startIndex at token boundaries (not on separator whitespace) - Add separatorMode: 'none' for agent-name completions from empty input - Add hasExactMatch mock and committed-past-boundary re-fetch tests
- Add TestSearchMenu class using real TST prefix tree instead of manually orchestrated jest.fn() mocks for isActive/updatePrefix/hasExactMatch - Add prefixTree.ts to shell src tsconfig for test imports - Fix TSTNode property types (optional -> explicit undefined) for exactOptionalPropertyTypes compatibility - Remove 49 mock orchestration lines across all tests - Add closedSet: true to explicit-mode tests where unique satisfaction makes isActive naturally false (prevents C6 interference)
- Add debug tracing to all decision points in reuseSession() (A1-A3, B4 explicit-deferred, no-position, C6 final decision) - Restore original hide() behavior: only cancel in-flight fetch and hide menu, preserving anchor/separatorMode/closedSet/commitMode so the session can be reused if update() is called again - Fix corrupted comment on line 196 (JSON replacement artifacts)
When uniquelySatisfied=true and commitMode=explicit, reuseSession() fell through to C6 where closedSet=false incorrectly triggered a re-fetch. Return true at B4 to hold the session until an explicit separator arrives. Add test for commitMode=explicit + closedSet=false + unique match.
- Fix bug: stripLeadingSeparator() properly strips punctuation in spacePunctuation mode instead of only whitespace via trimStart() - Add tests for separatorMode 'spacePunctuation' and 'optional' - Add tests for emojiChar propagation from groups to menu items - Add tests for backend error handling (rejected promises) - Add tests for startIndex edge cases (negative, zero) - Add tests for mixed sorted/unsorted group ordering - Fix outdated hide() test to match anchor-preserving semantics - Add tsc:model to shell build script for test compilation
Split monolithic partialCompletionSession.spec.ts (70 tests) into 6 themed spec files under test/partialCompletion/: - stateTransitions.spec.ts: IDLE/PENDING/ACTIVE transitions, closedSet, hide() - resultProcessing.spec.ts: startIndex, groups, needQuotes, emoji, edge cases - separatorMode.spec.ts: space, spacePunctuation, optional modes - commitMode.spec.ts: explicit vs eager, B5 boundary, hasExactMatch - publicAPI.spec.ts: getCompletionPrefix, resetToIdle, @command routing - errorHandling.spec.ts: rejected promises, recovery Shared test infrastructure extracted to helpers.ts.
Move function implementations out of the root SDK type files (command.ts, display.ts) into their respective helper modules (commandHelpers.ts, displayHelpers.ts), following the convention that root SDK exports are type-only and functionality lives in helpers. Update all consumers to import from @typeagent/agent-sdk/helpers/command and @typeagent/agent-sdk/helpers/display respectively.
…h, clean up session Option C: Replace exported GrammarSeparatorMode with local SeparatorMode in actionGrammar (includes 'space'; independently defined to avoid agentSdk dep). Option D: Rename prefixLength to matchedPrefixLength in CompletionGroups and all consumers (dispatcher, command handlers, tests) for clarity. partialCompletionSession: extract separator helpers to module-level functions, inline resetSessionFields(), trim redundant field JSDoc, fix error handler to preserve anchor on rejection so same-input calls reuse the session.
Bug 1: Apply tokenBoundary() in the fallback back-up so startIndex lands at the end of the preceding token (before separator whitespace), matching the convention every other code path follows. startIndex when the agent was already invoked for next-param completions — prevents mismatch between startIndex and completions when the last consumed parameter is a non-string type (e.g. number). Add numstrtest agent (number+string args) and 5 new test cases covering both bugs.
…eterCompletion Extract pure decision logic into resolveCompletionTarget that determines what to complete and where, with no agent I/O. The main function becomes a simple pipeline: parse → resolve target → invoke agent → compute closedSet. Restructure spec cases so 3a covers both 'still editing' sub-cases: 3a-i: free-form parameter value (isPartialValue: true) 3a-ii: uncommitted flag name (isPartialValue: false) 3b: last token committed, complete next Rename isCurrentToken to isPartialValue to clarify its semantic: it indicates free-form text (driving closedSet=false), not just whether startIndex points at the current token.
Case 3a depends on lastCompletableParam (string-type only) and pendingFlag. Number, boolean, and json params leave lastCompletableParam undefined, so they fall through to 3b even without trailing space. This is acceptable because non-string values are not meaningful targets for prefix-match completion.
Replace getPendingFlag (which pushed boolean completions as a side
effect) with detectPendingFlag returning {pendingFlag, booleanFlagName}.
Remove completions array from CompletionTarget; add includeFlags and
booleanFlagName metadata fields instead. resolveCompletionTarget now
returns only decisions — no collectFlags calls, no completion building.
The caller (getCommandParameterCompletion) materialises completions
from the target metadata in a new step 2, before invoking the agent.
… mode, and closed set - Narrow requestPrefix from string|undefined to string throughout the completion pipeline (cache, grammarStore, constructionStore, dispatcher). Callers pass empty string instead of undefined. - Rework ConstructionCache.completion() to track maxPrefixLength across all matching constructions, discarding shorter-match candidates. Report separatorMode and closedSet for downstream UI use. - Change mergeCompletionResults to eagerly discard completions from the shorter-prefix source instead of always merging both. - Propagate matchedCurrent character offset from partial matching so the completion layer knows exactly how far matching consumed. Accept partial matches even when remaining text has non-separator characters (the UI filters by remaining text). - Filter referential phrases from captured parts in completion candidates. - Move command prefix "@" to only appear when startIndex is 0 (first token), with separatorMode optional and commitMode eager. - Add comprehensive ConstructionCache.completion() tests and update mergeCompletionResults tests for the new discard behavior. - Remove unused getPrefix() method. - Fix typo: restor -> restore.
- Fix end-with-wildcard path in finishMatchParts to use isRejectReference() instead of config.rejectReferences directly, consistent with the middle-wildcard path. This prevents Entity wildcards from incorrectly rejecting referential phrases. - Add partial-mode wildcard advancement in finishMatchParts so completion can look past wildcard parts for subsequent literal parts, matching grammar matcher behaviour. - Add comprehensive wildcard completion tests covering entity wildcards, wildcards in middle of constructions, wildcard-enabled parts with literal matches, and constructions starting with wildcards.
- Update ParameterCompletionResult.closedSet comment: agents CAN now signal closedSet via CompletionGroups (no longer "cannot yet signal") - Add ?? "" fallback in translateCommandHandler and matchCommandHandler for requestPrefix passed to requestCompletion(string) (matches requestCommandHandler) - Remove dead requestPrefix ?? "" in grammarStore (param is now string) - Clarify "Array of CompletionGroup items" in getCommandCompletion contract to avoid collision with CompletionGroups wrapper type name - Add "space" case to grammarMatcher's local mergeSeparatorMode so the priority chain handles all four SeparatorMode values - Document that grammarMatcher only produces spacePunctuation/optional/ none, never "space"
- Extract isEditingFreeFormValue() predicate for dense 3-way condition - Export needsSeparatorInAutoMode from actionGrammar, remove duplicate in cache - Extract completeDescriptor() from getCommandCompletion branch logic - Define ParameterCompletionResult as Omit<CommandCompletionResult, 'commitMode'> - Extract toMenuItems() in partialCompletionSession - Extract computeClosedSet() with per-branch comments - Add commitMode: 'eager' rationale comments in requestCompletion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the ad-hoc, client-side token-boundary heuristics for command completion with a structured, metadata-driven pipeline. The backend now reports what it matched (
startIndex), how completions relate to the prefix (separatorMode), whether the completion list is exhaustive (closedSet), and when to advance to the next level (commitMode). The shell and CLI consume this metadata through a formal state machine instead of string-splitting heuristics.69 files changed, ~8,000 lines added, ~700 removed across 5 packages. 14 new test files totaling 5,637 lines of unit test coverage.
End-to-End Architecture
The completion feature spans 5 layers. A user keystroke flows top-to-bottom; metadata flows bottom-to-top.
Component Responsibilities
1. Grammar Matcher (
packages/actionGrammar/src/grammarMatcher.ts)Responsibility: Given a partial input string and compiled grammar rules, compute the set of valid next-tokens using longest-match semantics.
Behavior:
MatchStateper top-level grammar rulemaxPrefixLength— the furthest point any rule consumedmaxPrefixLengthadvances, all shorter-prefix completions are discarded (only the longest match contributes)tryPartialStringMatch(): multi-word string parts (e.g.["play", "shuffle"]) are offered one word at a time instead of all-at-onceseparatorModeper completion candidate using character-boundary analysis — determines whether space, punctuation, or nothing is needed between prefix and completionclosedSet = falsewhen property/wildcard (entity) completions are emitted (external values can't be enumerated){ completions, properties, matchedPrefixLength, separatorMode, closedSet }2. Cache Layer (
packages/cache/src/constructions/constructionCache.ts,grammarStore.ts)Responsibility: Merge completion results from construction-based matching and grammar-based matching, keeping only the longest-matched-prefix candidates.
Behavior:
match(prefix, options, partial=true), tracksmaxPrefixLength, eagerly discards shorter-prefix results. SetsclosedSet = falsewhen property completions exist.matchGrammarCompletion. AND-mergesclosedSetacross grammar results (only closed if ALL are closed).mergeCompletionResults()combines construction + grammar results: keeps the longer prefix, AND-mergesclosedSet, OR-mergesseparatorMode(strongest wins).cache.completion(): if_useNFAGrammar, grammar-only; otherwise merge both sources.3. Agent SDK (
packages/agentSdk/src/command.ts,helpers/commandHelpers.ts)Responsibility: Define the completion contract between dispatcher and agents. Agents return domain-specific completions with rich metadata.
New types:
SeparatorMode—"space" | "spacePunctuation" | "optional" | "none"— what separator is required between prefix and completion textCommitMode—"explicit" | "eager"— when uniquely-satisfied completion triggers next-level re-fetchCompletionGroups— wrapper aroundCompletionGroup[]addingmatchedPrefixLength,separatorMode,closedSet,commitModeAppAgentCommandInterface.getCommandCompletion()now returnsCompletionGroups(wasCompletionGroup[])Helper:
mergeSeparatorMode(a, b)— priority-based merge where the strongest separator requirement wins (space > spacePunctuation > optional > none)Type cleanup: Internal parsing fields (
tokens,lastCompletableParam,lastParamImplicitQuotes,nextArgs) moved from SDK'sParsedCommandParamsto dispatcher-internalParseParamsResult, keeping the public SDK surface clean.4. Dispatcher (
packages/dispatcher/dispatcher/src/command/completion.ts,parameters.ts)Responsibility: Orchestrate command resolution, parameter parsing, agent invocation, and built-in completions into a single
CommandCompletionResult.Key functions and behavior:
getCommandCompletion(input, context)— Top-level entry point. Never fails (always returns a result). Three-way branch:separatorMode="none")completeDescriptor()for parameter + subcommand completionsstartIndex === 0→ prepend@as Command Prefix withcommitMode="eager"resolveCompletionTarget(params, flags, input, hasTrailingSpace)— Pure decision engine:remainderLength > 0): offer what follows longest valid prefixCompletionTargetwithcompletionNames,startIndex,isPartialValue,includeFlags,booleanFlagNamegetCommandParameterCompletion()— Full parameter pipeline:ParseParamsResultcollectFlags())getCommandCompletion()if available → agent can overridestartIndexviamatchedPrefixLengthclosedSetviacomputeClosedSet()heuristic (agent authoritative when invoked; free-form → open; no remaining args → closed)CommandCompletionResultreturned to shell/CLI:5. Shell —
PartialCompletionSession(packages/shell/src/renderer/src/partialCompletionSession.ts)Responsibility: State machine managing the lifecycle of a completion interaction. Decides when to re-fetch from the backend vs. filter locally.
States:
IDLE → PENDING → ACTIVE(and back to IDLE on reset)Key behavior —
reuseSession(input)decision tree:closedSet=false)Key methods:
update(input, getPosition)— Main entry called by DOM adapter on every keystrokestartNewSession(input, getPosition)— Issues backend request; on result, sets anchor, populates menu, computescompletionPrefix, shows menugetCompletionPrefix(input)— Returns typed text after anchor, stripping leading separators perseparatorModehide()— Cancels in-flight fetch but preserves session state (reusable on re-focus)resetToIdle()— Full state wipeHelper functions:
requiresSeparator(mode)— Boolean: does this mode need a separator?separatorRegex(mode)— Regex for the separator character classstripLeadingSeparator(rawPrefix, mode)— Removes leading separator from prefixtoMenuItems(groups)— ConvertsCompletionGroup[]→ flatSearchMenuItem[]preserving group order6. Shell —
PartialCompletion(packages/shell/src/renderer/src/partial.ts)Responsibility: DOM adapter bridging the text editor and the session state machine. Handles all browser-side concerns.
Behavior:
SearchMenu→ISearchMenuandDispatcher→ICompletionDispatcheradaptersupdate(contentChanged)— checks cursor position, normalizes text, delegates tosession.update()handleSelect(item)— DOM manipulation: computes replacement range from completion prefix, deletes old text, inserts selected text, repositions cursor, triggers fresh completiongetSearchMenuPosition(prefix)— calculates pixel coordinates for menu positioning using DOM Range API7. Shell —
SearchMenuBase(packages/shell/src/renderer/src/searchMenuBase.ts)Responsibility: Abstract base class providing trie-backed prefix filtering, extracted from
SearchMenuto enable testability.Behavior:
setChoices(choices)— Populates TST (ternary search tree) from items, deduplicates by normalized textupdatePrefix(prefix, position)— Queries trie, returnstrueif unique/exact match; callsonShow()/onHide()template methodshasExactMatch(text)— Tests exact trie membershipnormalizeMatchText(text)— NFD normalization, diacritics removal, case foldingPrevious Architecture vs. New Architecture
matchedPrefixLength→startIndex, eliminating client-side splitting.input.startsWith(current)) +noCompletionflag. Re-fetched when trie emptied regardless of reason.closedSetsuppresses re-fetch when trie empties on finite sets.@commands used space-based boundaries; grammar completions usedlastIndexOf(" ")to find boundaries. CJK/non-space scripts unsupported.separatorModeflows from grammar through cache/agent/dispatcher to shell. Four modes support Latin (space/spacePunctuation), CJK (optional), and[spacing=none]grammars (none).commitModefield:"eager"(re-fetch on unique match, used for@prefix) vs."explicit"(user must type delimiter, default for parameter completions).closedSetboolean flows through entire pipeline. When true, shell skips re-fetch on trie exhaustion. Grammar entities forceclosedSet=false; pure keyword alternatives remaintrue.CompletionGroup[]flat array returned by agents. No wrapper metadata. Completion fields (tokens,lastCompletableParam, etc.) mixed into SDK'sParsedCommandParams.CompletionGroupswrapper with{ groups, matchedPrefixLength, separatorMode, closedSet, commitMode }. Internal parsing fields moved to dispatcher-onlyParseParamsResult."shuffle mode"as one completion).tryPartialStringMatch()offers one word at a time, guiding users through multi-word phrases progressively.PartialCompletion:current(anchor string),noCompletion(boolean),completionP(pending promise). State transitions implicit in method flow.PartialCompletionSessionclass with explicitIDLE/PENDING/ACTIVEstates, typed anchor/separator/commit metadata, and documented decision tree.SearchMenuwas concrete and DOM-coupled. No unit tests for completion session logic.SearchMenuBaseextracted as abstract base.TestSearchMenuuses real trie logic. 6 test files cover session state transitions, commit modes, separator modes, result processing, error handling, and public API.Packages Modified
matchGrammarCompletionrewritten: longest-match prioritization,tryPartialStringMatchfor word-by-word completion,closedSet/separatorMode/matchedPrefixLengthoutput. 4 new test files (1,364 lines).SeparatorMode,CommitMode,CompletionGroups. Agent return type changed fromCompletionGroup[]→CompletionGroups.mergeSeparatorMode()helper. Internal parsing fields removed fromParsedCommandParams.constructionCache.completion()returnsclosedSet/separatorMode/matchedPrefixLength.mergeCompletionResults()implements AND-merge forclosedSet, longest-prefix wins. 2 new test files (1,144 lines).getCommandCompletion()returns fullCommandCompletionResultwithclosedSet/separatorMode/commitMode.resolveCompletionTarget()extracted as pure decision engine.computeClosedSet()heuristic.parseParamsextended withParseParamsResult. 2 new test files (1,660 lines).PartialCompletionSessionextracted (440 lines).PartialCompletionsimplified from 416 → 280 lines.SearchMenuBaseextracted (91 lines). 6 new test files (1,469 lines). Jest config added.separatorModeconsumed to insert separator in readline display.CompletionGroupsreturn type (browser, taskflow).