mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-28 07:16:53 +05:00
feat(ipuaro): add enum value definitions to initial context
Extract enum declarations with member names and values from TypeScript AST and display them in the initial LLM context. This allows the LLM to know valid enum values without making tool calls. Features: - Numeric values (Active=1) - String values (Admin="admin") - Implicit values (Up, Down) - Negative numbers (Cold=-10) - const enum modifier - export enum modifier - Long enum truncation (>100 chars) Adds EnumInfo and EnumMemberInfo interfaces, extractEnum() method in ASTParser, formatEnumSignature() in prompts.ts, and 17 new unit tests.
This commit is contained in:
@@ -133,6 +133,28 @@ export interface TypeAliasInfo {
|
||||
definition?: string
|
||||
}
|
||||
|
||||
export interface EnumMemberInfo {
|
||||
/** Member name */
|
||||
name: string
|
||||
/** Member value (string or number, if specified) */
|
||||
value?: string | number
|
||||
}
|
||||
|
||||
export interface EnumInfo {
|
||||
/** Enum name */
|
||||
name: string
|
||||
/** Start line number */
|
||||
lineStart: number
|
||||
/** End line number */
|
||||
lineEnd: number
|
||||
/** Enum members with values */
|
||||
members: EnumMemberInfo[]
|
||||
/** Whether it's exported */
|
||||
isExported: boolean
|
||||
/** Whether it's a const enum */
|
||||
isConst: boolean
|
||||
}
|
||||
|
||||
export interface FileAST {
|
||||
/** Import statements */
|
||||
imports: ImportInfo[]
|
||||
@@ -146,6 +168,8 @@ export interface FileAST {
|
||||
interfaces: InterfaceInfo[]
|
||||
/** Type alias declarations */
|
||||
typeAliases: TypeAliasInfo[]
|
||||
/** Enum declarations */
|
||||
enums: EnumInfo[]
|
||||
/** Whether parsing encountered errors */
|
||||
parseError: boolean
|
||||
/** Parse error message if any */
|
||||
@@ -160,6 +184,7 @@ export function createEmptyFileAST(): FileAST {
|
||||
classes: [],
|
||||
interfaces: [],
|
||||
typeAliases: [],
|
||||
enums: [],
|
||||
parseError: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import JSON from "tree-sitter-json"
|
||||
import * as yamlParser from "yaml"
|
||||
import {
|
||||
createEmptyFileAST,
|
||||
type EnumMemberInfo,
|
||||
type ExportInfo,
|
||||
type FileAST,
|
||||
type ImportInfo,
|
||||
@@ -192,6 +193,11 @@ export class ASTParser {
|
||||
this.extractTypeAlias(node, ast, false)
|
||||
}
|
||||
break
|
||||
case NodeType.ENUM_DECLARATION:
|
||||
if (isTypeScript) {
|
||||
this.extractEnum(node, ast, false)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +281,10 @@ export class ASTParser {
|
||||
this.extractTypeAlias(declaration, ast, true)
|
||||
this.addExportInfo(ast, declaration, "type", isDefault)
|
||||
break
|
||||
case NodeType.ENUM_DECLARATION:
|
||||
this.extractEnum(declaration, ast, true)
|
||||
this.addExportInfo(ast, declaration, "type", isDefault)
|
||||
break
|
||||
case NodeType.LEXICAL_DECLARATION:
|
||||
this.extractLexicalDeclaration(declaration, ast, true)
|
||||
break
|
||||
@@ -565,6 +575,75 @@ export class ASTParser {
|
||||
})
|
||||
}
|
||||
|
||||
private extractEnum(node: SyntaxNode, ast: FileAST, isExported: boolean): void {
|
||||
const nameNode = node.childForFieldName(FieldName.NAME)
|
||||
if (!nameNode) {
|
||||
return
|
||||
}
|
||||
|
||||
const body = node.childForFieldName(FieldName.BODY)
|
||||
const members: EnumMemberInfo[] = []
|
||||
|
||||
if (body) {
|
||||
for (const child of body.children) {
|
||||
if (child.type === NodeType.ENUM_ASSIGNMENT) {
|
||||
const memberName = child.childForFieldName(FieldName.NAME)
|
||||
const memberValue = child.childForFieldName(FieldName.VALUE)
|
||||
if (memberName) {
|
||||
members.push({
|
||||
name: memberName.text,
|
||||
value: this.parseEnumValue(memberValue),
|
||||
})
|
||||
}
|
||||
} else if (
|
||||
child.type === NodeType.IDENTIFIER ||
|
||||
child.type === NodeType.PROPERTY_IDENTIFIER
|
||||
) {
|
||||
members.push({
|
||||
name: child.text,
|
||||
value: undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isConst = node.children.some((c) => c.text === "const")
|
||||
|
||||
ast.enums.push({
|
||||
name: nameNode.text,
|
||||
lineStart: node.startPosition.row + 1,
|
||||
lineEnd: node.endPosition.row + 1,
|
||||
members,
|
||||
isExported,
|
||||
isConst,
|
||||
})
|
||||
}
|
||||
|
||||
private parseEnumValue(valueNode: SyntaxNode | null): string | number | undefined {
|
||||
if (!valueNode) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const text = valueNode.text
|
||||
|
||||
if (valueNode.type === "number") {
|
||||
return Number(text)
|
||||
}
|
||||
|
||||
if (valueNode.type === "string") {
|
||||
return this.getStringValue(valueNode)
|
||||
}
|
||||
|
||||
if (valueNode.type === "unary_expression" && text.startsWith("-")) {
|
||||
const num = Number(text)
|
||||
if (!isNaN(num)) {
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
private extractParameters(node: SyntaxNode): ParameterInfo[] {
|
||||
const params: ParameterInfo[] = []
|
||||
const paramsNode = node.childForFieldName(FieldName.PARAMETERS)
|
||||
|
||||
@@ -16,6 +16,7 @@ export const NodeType = {
|
||||
CLASS_DECLARATION: "class_declaration",
|
||||
INTERFACE_DECLARATION: "interface_declaration",
|
||||
TYPE_ALIAS_DECLARATION: "type_alias_declaration",
|
||||
ENUM_DECLARATION: "enum_declaration",
|
||||
|
||||
// Clauses
|
||||
IMPORT_CLAUSE: "import_clause",
|
||||
@@ -37,6 +38,11 @@ export const NodeType = {
|
||||
FIELD_DEFINITION: "field_definition",
|
||||
PROPERTY_SIGNATURE: "property_signature",
|
||||
|
||||
// Enum members
|
||||
ENUM_BODY: "enum_body",
|
||||
ENUM_ASSIGNMENT: "enum_assignment",
|
||||
PROPERTY_IDENTIFIER: "property_identifier",
|
||||
|
||||
// Parameters
|
||||
REQUIRED_PARAMETER: "required_parameter",
|
||||
OPTIONAL_PARAMETER: "optional_parameter",
|
||||
|
||||
@@ -240,6 +240,37 @@ function formatTypeAliasSignature(type: FileAST["typeAliases"][0]): string {
|
||||
return `type ${type.name} = ${definition}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an enum signature with members and values.
|
||||
* Example: "enum Status { Active=1, Inactive=0, Pending=2 }"
|
||||
* Example: "const enum Role { Admin="admin", User="user" }"
|
||||
*/
|
||||
function formatEnumSignature(enumInfo: FileAST["enums"][0]): string {
|
||||
const constPrefix = enumInfo.isConst ? "const " : ""
|
||||
|
||||
if (enumInfo.members.length === 0) {
|
||||
return `${constPrefix}enum ${enumInfo.name}`
|
||||
}
|
||||
|
||||
const membersStr = enumInfo.members
|
||||
.map((m) => {
|
||||
if (m.value === undefined) {
|
||||
return m.name
|
||||
}
|
||||
const valueStr = typeof m.value === "string" ? `"${m.value}"` : String(m.value)
|
||||
return `${m.name}=${valueStr}`
|
||||
})
|
||||
.join(", ")
|
||||
|
||||
const result = `${constPrefix}enum ${enumInfo.name} { ${membersStr} }`
|
||||
|
||||
if (result.length > 100) {
|
||||
return truncateDefinition(result, 100)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate long type definitions for display.
|
||||
*/
|
||||
@@ -297,6 +328,12 @@ function formatFileSummary(
|
||||
}
|
||||
}
|
||||
|
||||
if (ast.enums && ast.enums.length > 0) {
|
||||
for (const enumInfo of ast.enums) {
|
||||
lines.push(`- ${formatEnumSignature(enumInfo)}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (lines.length === 1) {
|
||||
return `- ${path}${flags}`
|
||||
}
|
||||
@@ -330,6 +367,11 @@ function formatFileSummaryCompact(path: string, ast: FileAST, flags: string): st
|
||||
parts.push(`type: ${names}`)
|
||||
}
|
||||
|
||||
if (ast.enums && ast.enums.length > 0) {
|
||||
const names = ast.enums.map((e) => e.name).join(", ")
|
||||
parts.push(`enum: ${names}`)
|
||||
}
|
||||
|
||||
const summary = parts.length > 0 ? ` [${parts.join(" | ")}]` : ""
|
||||
return `- ${path}${summary}${flags}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user