keyword.ts
utils/ultraplan/keyword.ts
128
Lines
4691
Bytes
5
Exports
0
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with planner-verifier-agents. It contains 128 lines, 0 detected imports, and 5 detected exports.
Important relationships
Detected exports
findUltraplanTriggerPositionsfindUltrareviewTriggerPositionshasUltraplanKeywordhasUltrareviewKeywordreplaceUltraplanKeyword
Keywords
textstartultraplanaftercontinueopenquotebeforematchtriggerlength
Detected imports
- No import paths detected.
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
type TriggerPosition = { word: string; start: number; end: number }
const OPEN_TO_CLOSE: Record<string, string> = {
'`': '`',
'"': '"',
'<': '>',
'{': '}',
'[': ']',
'(': ')',
"'": "'",
}
/**
* Find keyword positions, skipping occurrences that are clearly not a
* launch directive:
*
* - Inside paired delimiters: backticks, double quotes, angle brackets
* (tag-like only, so `n < 5 ultraplan n > 10` is not a phantom range),
* curly braces, square brackets (innermost — preExpansionInput has
* `[Pasted text #N]` placeholders), parentheses. Single quotes are
* delimiters only when not an apostrophe — the opening quote must be
* preceded by a non-word char (or start) and the closing quote must be
* followed by a non-word char (or end), so "let's ultraplan it's"
* still triggers.
*
* - Path/identifier-like context: immediately preceded or followed by
* `/`, `\`, or `-`, or followed by `.` + word char (file extension).
* `\b` sees a boundary at `-`, so `ultraplan-s` would otherwise
* match. This keeps `src/ultraplan/foo.ts`, `ultraplan.tsx`, and
* `--ultraplan-mode` from triggering while `ultraplan.` at a sentence
* end still does.
*
* - Followed by `?`: a question about the feature shouldn't invoke it.
* Other sentence punctuation (`.`, `,`, `!`) still triggers.
*
* - Slash command input: text starting with `/` is a slash command
* invocation (processUserInput.ts routes it to processSlashCommand,
* not keyword detection), so `/rename ultraplan foo` never triggers.
* Without this, PromptInput would rainbow-highlight the word and show
* the "will launch ultraplan" notification even though submitting the
* input runs /rename, not /ultraplan.
*
* Shape matches findThinkingTriggerPositions (thinking.ts) so
* PromptInput treats both trigger types uniformly.
*/
function findKeywordTriggerPositions(
text: string,
keyword: string,
): TriggerPosition[] {
const re = new RegExp(keyword, 'i')
if (!re.test(text)) return []
if (text.startsWith('/')) return []
const quotedRanges: Array<{ start: number; end: number }> = []
let openQuote: string | null = null
let openAt = 0
const isWord = (ch: string | undefined) => !!ch && /[\p{L}\p{N}_]/u.test(ch)
for (let i = 0; i < text.length; i++) {
const ch = text[i]!
if (openQuote) {
if (openQuote === '[' && ch === '[') {
openAt = i
continue
}
if (ch !== OPEN_TO_CLOSE[openQuote]) continue
if (openQuote === "'" && isWord(text[i + 1])) continue
quotedRanges.push({ start: openAt, end: i + 1 })
openQuote = null
} else if (
(ch === '<' && i + 1 < text.length && /[a-zA-Z/]/.test(text[i + 1]!)) ||
(ch === "'" && !isWord(text[i - 1])) ||
(ch !== '<' && ch !== "'" && ch in OPEN_TO_CLOSE)
) {
openQuote = ch
openAt = i
}
}
const positions: TriggerPosition[] = []
const wordRe = new RegExp(`\\b${keyword}\\b`, 'gi')
const matches = text.matchAll(wordRe)
for (const match of matches) {
if (match.index === undefined) continue
const start = match.index
const end = start + match[0].length
if (quotedRanges.some(r => start >= r.start && start < r.end)) continue
const before = text[start - 1]
const after = text[end]
if (before === '/' || before === '\\' || before === '-') continue
if (after === '/' || after === '\\' || after === '-' || after === '?')
continue
if (after === '.' && isWord(text[end + 1])) continue
positions.push({ word: match[0], start, end })
}
return positions
}
export function findUltraplanTriggerPositions(text: string): TriggerPosition[] {
return findKeywordTriggerPositions(text, 'ultraplan')
}
export function findUltrareviewTriggerPositions(
text: string,
): TriggerPosition[] {
return findKeywordTriggerPositions(text, 'ultrareview')
}
export function hasUltraplanKeyword(text: string): boolean {
return findUltraplanTriggerPositions(text).length > 0
}
export function hasUltrareviewKeyword(text: string): boolean {
return findUltrareviewTriggerPositions(text).length > 0
}
/**
* Replace the first triggerable "ultraplan" with "plan" so the forwarded
* prompt stays grammatical ("please ultraplan this" → "please plan this").
* Preserves the user's casing of the "plan" suffix.
*/
export function replaceUltraplanKeyword(text: string): string {
const [trigger] = findUltraplanTriggerPositions(text)
if (!trigger) return text
const before = text.slice(0, trigger.start)
const after = text.slice(trigger.end)
if (!(before + after).trim()) return ''
return before + trigger.word.slice('ultra'.length) + after
}