Skip to content

Structured completion pipeline with semantic metadata#2007

Draft
curtisman wants to merge 53 commits intomicrosoft:mainfrom
curtisman:completion
Draft

Structured completion pipeline with semantic metadata#2007
curtisman wants to merge 53 commits intomicrosoft:mainfrom
curtisman:completion

Conversation

@curtisman
Copy link
Member

@curtisman curtisman commented Mar 13, 2026

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.

TODO: DFA/NFA grammar matcher work is not done for this architectural change. The current NFA/DFA paths in the grammar store pass through completions but do not yet produce closedSet, separatorMode, or matchedPrefixLength metadata — only the simple matchGrammarCompletion path is fully updated.


End-to-End Architecture

The completion feature spans 5 layers. A user keystroke flows top-to-bottom; metadata flows bottom-to-top.

┌─────────────────────────────────────────────────────────┐
│  Shell (renderer) / CLI                                 │
│  PartialCompletionSession  ←  state machine             │
│  PartialCompletion         ←  DOM adapter               │
│  SearchMenuBase            ←  trie-backed filter        │
├─────────────────────────────────────────────────────────┤
│  Dispatcher                                             │
│  getCommandCompletion()    ←  orchestration entry point │
│  resolveCompletionTarget() ←  decision engine           │
│  getCommandParameterCompletion() ← parameter pipeline   │
├─────────────────────────────────────────────────────────┤
│  Agent SDK                                              │
│  AppAgentCommandInterface.getCommandCompletion()        │
│  CompletionGroups (return type with metadata)           │
├─────────────────────────────────────────────────────────┤
│  Cache (Construction + Grammar Store)                   │
│  constructionCache.completion() → mergeCompletionResults│
│  grammarStore.completion()  → DFA > NFA > simple        │
├─────────────────────────────────────────────────────────┤
│  Grammar Matcher (actionGrammar)                        │
│  matchGrammarCompletion()  ←  longest-match + word-by-  │
│  tryPartialStringMatch()      word progression          │
└─────────────────────────────────────────────────────────┘

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:

  • Seeds a work-list with one MatchState per top-level grammar rule
  • Greedily advances each state through the prefix, tracking maxPrefixLength — the furthest point any rule consumed
  • When maxPrefixLength advances, all shorter-prefix completions are discarded (only the longest match contributes)
  • Categorizes outcomes into:
    • Category 1 — Exact match (entire rule matched, prefix fully consumed)
    • Category 2 — Partial match, clean boundary (prefix consumed, rule has remaining parts → offer next part)
    • Category 3 — Partial match, dirty (trailing non-separator text or pending wildcard)
  • Word-by-word progression via tryPartialStringMatch(): multi-word string parts (e.g. ["play", "shuffle"]) are offered one word at a time instead of all-at-once
  • Computes separatorMode per completion candidate using character-boundary analysis — determines whether space, punctuation, or nothing is needed between prefix and completion
  • Sets closedSet = false when property/wildcard (entity) completions are emitted (external values can't be enumerated)
  • Returns { 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:

  • Construction cache calls match(prefix, options, partial=true), tracks maxPrefixLength, eagerly discards shorter-prefix results. Sets closedSet = false when property completions exist.
  • Grammar store tries three strategies in priority order: DFA → NFA → simple matchGrammarCompletion. AND-merges closedSet across grammar results (only closed if ALL are closed).
  • mergeCompletionResults() combines construction + grammar results: keeps the longer prefix, AND-merges closedSet, OR-merges separatorMode (strongest wins).
  • Top-level 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 text
  • CommitMode"explicit" | "eager" — when uniquely-satisfied completion triggers next-level re-fetch
  • CompletionGroups — wrapper around CompletionGroup[] adding matchedPrefixLength, separatorMode, closedSet, commitMode
  • AppAgentCommandInterface.getCommandCompletion() now returns CompletionGroups (was CompletionGroup[])

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's ParsedCommandParams to dispatcher-internal ParseParamsResult, 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:

    1. Uncommitted command — last command token matched but no trailing space → offer sibling subcommands (separatorMode="none")
    2. Resolved descriptorcompleteDescriptor() for parameter + subcommand completions
    3. Unresolved table → offer subcommand names as trie filter candidates
    • Special: startIndex === 0 → prepend @ as Command Prefix with commitMode="eager"
  • resolveCompletionTarget(params, flags, input, hasTrailingSpace) — Pure decision engine:

    • Spec case 1 — Partial parse (remainderLength > 0): offer what follows longest valid prefix
    • Spec case 3a — Full parse, last token uncommitted: either editing free-form value (3a-i, string type only) or uncommitted flag name (3a-ii)
    • Spec case 3b — Full parse, last token committed: complete next parameter/flag
    • Returns CompletionTarget with completionNames, startIndex, isPartialValue, includeFlags, booleanFlagName
  • getCommandParameterCompletion() — Full parameter pipeline:

    • Parses parameters partially → ParseParamsResult
    • Resolves completion target
    • Materializes built-in completions (boolean flag values, flag names via collectFlags())
    • Invokes agent getCommandCompletion() if available → agent can override startIndex via matchedPrefixLength
    • Computes final closedSet via computeClosedSet() heuristic (agent authoritative when invoked; free-form → open; no remaining args → closed)
  • CommandCompletionResult returned to shell/CLI:

    { startIndex, completions[], separatorMode, closedSet, commitMode }
    

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:

Code Trigger Category Action
A1 No active session (IDLE) Invalidation Re-fetch
A2 Input moved past anchor Invalidation Re-fetch
A3 Non-separator typed when separator required Invalidation Re-fetch
B4 Unique match + eager commit mode Navigation Re-fetch (next level)
B5 Committed past boundary (separator after exact match) Navigation Re-fetch (next level)
C6 No menu matches + open set (closedSet=false) Discovery Re-fetch
Menu has matches Reuse (filter locally)
No matches + closed set Reuse (menu hidden, no re-fetch)

Key methods:

  • update(input, getPosition) — Main entry called by DOM adapter on every keystroke
  • startNewSession(input, getPosition) — Issues backend request; on result, sets anchor, populates menu, computes completionPrefix, shows menu
  • getCompletionPrefix(input) — Returns typed text after anchor, stripping leading separators per separatorMode
  • hide() — Cancels in-flight fetch but preserves session state (reusable on re-focus)
  • resetToIdle() — Full state wipe

Helper functions:

  • requiresSeparator(mode) — Boolean: does this mode need a separator?
  • separatorRegex(mode) — Regex for the separator character class
  • stripLeadingSeparator(rawPrefix, mode) — Removes leading separator from prefix
  • toMenuItems(groups) — Converts CompletionGroup[] → flat SearchMenuItem[] preserving group order

6. 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:

  • Wraps SearchMenuISearchMenu and DispatcherICompletionDispatcher adapters
  • update(contentChanged) — checks cursor position, normalizes text, delegates to session.update()
  • handleSelect(item) — DOM manipulation: computes replacement range from completion prefix, deletes old text, inserts selected text, repositions cursor, triggers fresh completion
  • getSearchMenuPosition(prefix) — calculates pixel coordinates for menu positioning using DOM Range API

7. Shell — SearchMenuBase (packages/shell/src/renderer/src/searchMenuBase.ts)

Responsibility: Abstract base class providing trie-backed prefix filtering, extracted from SearchMenu to enable testability.

Behavior:

  • setChoices(choices) — Populates TST (ternary search tree) from items, deduplicates by normalized text
  • updatePrefix(prefix, position) — Queries trie, returns true if unique/exact match; calls onShow()/onHide() template methods
  • hasExactMatch(text) — Tests exact trie membership
  • normalizeMatchText(text) — NFD normalization, diacritics removal, case folding

Previous Architecture vs. New Architecture

Aspect Previous (origin/main) New (this branch)
Token-boundary logic Client-side heuristic: shell and CLI manually split input on spaces, only sent "complete tokens" to backend. Mid-word input was locally filtered or held until next space. Backend-driven: full input sent to dispatcher. Grammar matcher reports matchedPrefixLengthstartIndex, eliminating client-side splitting.
Re-fetch decision Simple string-prefix check (input.startsWith(current)) + noCompletion flag. Re-fetched when trie emptied regardless of reason. Formal state machine with 6 categorized triggers (invalidation A1–A3, navigation B4–B5, discovery C6). closedSet suppresses re-fetch when trie empties on finite sets.
Separator handling Hardcoded: @ commands used space-based boundaries; grammar completions used lastIndexOf(" ") to find boundaries. CJK/non-space scripts unsupported. Metadata-driven: separatorMode flows from grammar through cache/agent/dispatcher to shell. Four modes support Latin (space/spacePunctuation), CJK (optional), and [spacing=none] grammars (none).
Commit behavior Implicit: unique match always triggered re-fetch (eager). No way for agents to suppress it. Explicit commitMode field: "eager" (re-fetch on unique match, used for @ prefix) vs. "explicit" (user must type delimiter, default for parameter completions).
Closed-set signaling None. When trie emptied, shell always re-fetched backend hoping for new results, even for finite enum parameters. closedSet boolean flows through entire pipeline. When true, shell skips re-fetch on trie exhaustion. Grammar entities force closedSet=false; pure keyword alternatives remain true.
Completion result format CompletionGroup[] flat array returned by agents. No wrapper metadata. Completion fields (tokens, lastCompletableParam, etc.) mixed into SDK's ParsedCommandParams. CompletionGroups wrapper with { groups, matchedPrefixLength, separatorMode, closedSet, commitMode }. Internal parsing fields moved to dispatcher-only ParseParamsResult.
Multi-word completions Grammar offered entire multi-word strings at once (e.g. "shuffle mode" as one completion). tryPartialStringMatch() offers one word at a time, guiding users through multi-word phrases progressively.
Session state Three loose fields in PartialCompletion: current (anchor string), noCompletion (boolean), completionP (pending promise). State transitions implicit in method flow. Dedicated PartialCompletionSession class with explicit IDLE/PENDING/ACTIVE states, typed anchor/separator/commit metadata, and documented decision tree.
Testability SearchMenu was concrete and DOM-coupled. No unit tests for completion session logic. SearchMenuBase extracted as abstract base. TestSearchMenu uses real trie logic. 6 test files cover session state transitions, commit modes, separator modes, result processing, error handling, and public API.
Test coverage No completion-specific tests. 5,637 lines across 14 new test files covering grammar matching, cache merging, dispatcher orchestration, and shell session behavior.

Packages Modified

Package Changes
actionGrammar matchGrammarCompletion rewritten: longest-match prioritization, tryPartialStringMatch for word-by-word completion, closedSet/separatorMode/matchedPrefixLength output. 4 new test files (1,364 lines).
agentSdk New types: SeparatorMode, CommitMode, CompletionGroups. Agent return type changed from CompletionGroup[]CompletionGroups. mergeSeparatorMode() helper. Internal parsing fields removed from ParsedCommandParams.
cache constructionCache.completion() returns closedSet/separatorMode/matchedPrefixLength. mergeCompletionResults() implements AND-merge for closedSet, longest-prefix wins. 2 new test files (1,144 lines).
dispatcher getCommandCompletion() returns full CommandCompletionResult with closedSet/separatorMode/commitMode. resolveCompletionTarget() extracted as pure decision engine. computeClosedSet() heuristic. parseParams extended with ParseParamsResult. 2 new test files (1,660 lines).
shell PartialCompletionSession extracted (440 lines). PartialCompletion simplified from 416 → 280 lines. SearchMenuBase extracted (91 lines). 6 new test files (1,469 lines). Jest config added.
cli Token-boundary heuristics removed. Full input sent to backend. separatorMode consumed to insert separator in readline display.
agents Callsites updated for CompletionGroups return type (browser, taskflow).

curtisman added 30 commits March 5, 2026 08:23
…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.
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant