refactor: remove unused YamlParser implementation

- Removed src/services/package-manager/YamlParser.ts
- Verified no test files or imports existed
- All tests passing (1263 pass, 0 fail, 23 pending)

Task ID: 4d3f7a2e-8c1b-4f9d-b5e2-9c1d8f3a6b4d
This commit is contained in:
Smartsheet-JB-Brown 2025-04-12 17:30:11 -07:00
parent 4417886324
commit 589b159687
10 changed files with 553 additions and 148 deletions

62
.roo/package-lock.json generated Normal file
View file

@ -0,0 +1,62 @@
{
"name": "roo-prepare",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "roo-prepare",
"version": "1.0.0",
"dependencies": {
"commander": "^11.1.0"
},
"bin": {
"prepare": "dist/prepare-cli.js"
},
"devDependencies": {
"@types/node": "^20.11.0",
"typescript": "^5.3.3"
}
},
"node_modules/@types/node": {
"version": "20.17.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz",
"integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
}
},
"node_modules/commander": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true,
"license": "MIT"
}
}
}

21
.roo/package.json Normal file
View file

@ -0,0 +1,21 @@
{
"name": "roo-prepare",
"version": "1.0.0",
"description": "Prepare for commit task management system",
"private": true,
"bin": {
"prepare": "./dist/prepare-cli.js"
},
"scripts": {
"build": "tsc",
"prepare": "npm run build",
"start": "node ./dist/prepare-cli.js"
},
"dependencies": {
"commander": "^11.1.0"
},
"devDependencies": {
"@types/node": "^20.11.0",
"typescript": "^5.3.3"
}
}

148
.roo/prepare-cli.ts Normal file
View file

@ -0,0 +1,148 @@
#!/usr/bin/env node
import { program } from "commander"
import { taskManager } from "./task-manager"
import * as fs from "fs/promises"
import * as path from "path"
import { execSync } from "child_process"
program.name("prepare").description("CLI to manage prepare for commit tasks")
program
.command("create <taskId>")
.description("Create a new task")
.requiredOption("-d, --description <description>", "Task description")
.action(async (taskId: string, options: { description: string }) => {
try {
const initialCommit = execSync("git rev-parse HEAD").toString().trim()
await taskManager.createTask(taskId, options.description, initialCommit)
console.log(`Created task: ${taskId}`)
} catch (error) {
console.error("Failed to create task:", error)
process.exit(1)
}
})
program
.command("switch <taskId>")
.description("Switch to a different task")
.action(async (taskId: string) => {
try {
await taskManager.switchTask(taskId)
const task = taskManager.getCurrentTask()
console.log(`Switched to task: ${taskId}`)
console.log("Current checkpoint:", task?.active_checkpoint)
if (task?.pending_decisions.length) {
console.log("\nPending decisions:")
task.pending_decisions.forEach((decision, i) => {
console.log(`${i + 1}. ${decision}`)
})
}
} catch (error) {
console.error("Failed to switch task:", error)
process.exit(1)
}
})
program
.command("list")
.description("List all tasks")
.action(async () => {
try {
const tasks = await taskManager.listTasks()
console.log("Available tasks:")
for (const taskId of tasks) {
const content = await fs.readFile(path.join(".roo", "prepare_logs", `${taskId}.json`), "utf-8")
const task = JSON.parse(content)
console.log(`- ${taskId}: ${task.description} (${task.status})`)
}
} catch (error) {
console.error("Failed to list tasks:", error)
process.exit(1)
}
})
program
.command("status")
.description("Show current task status")
.action(() => {
const task = taskManager.getCurrentTask()
if (!task) {
console.log("No active task")
return
}
console.log(`Current task: ${task.task_id}`)
console.log(`Description: ${task.description}`)
console.log(`Status: ${task.status}`)
console.log(`Active checkpoint: ${task.active_checkpoint}`)
if (task.pending_decisions.length) {
console.log("\nPending decisions:")
task.pending_decisions.forEach((decision, i) => {
console.log(`${i + 1}. ${decision}`)
})
}
console.log("\nTest results:")
console.log(
`- Unit tests: ${task.test_results.unit_tests.passing} passing, ${task.test_results.unit_tests.failing} failing`,
)
console.log(`- Linting: ${task.test_results.linting}`)
console.log(`- Manual testing: ${task.test_results.manual_testing}`)
console.log("\nRollback info:")
console.log(`Full rollback: ${task.rollback_info.full_rollback}`)
console.log("Partial rollbacks:")
Object.entries(task.rollback_info.partial_rollbacks).forEach(([name, command]) => {
console.log(`- ${name}: ${command}`)
})
})
program
.command("checkpoint")
.description("Create a new checkpoint")
.requiredOption("-d, --description <description>", "Checkpoint description")
.requiredOption("-c, --component <component>", "Component being modified")
.requiredOption("--changes <changes...>", "List of changes")
.requiredOption("--risks <risks...>", "List of risks")
.requiredOption("--feedback <feedback...>", "Expected user feedback")
.action(async (options) => {
try {
const commitHash = execSync("git rev-parse HEAD").toString().trim()
const task = taskManager.getCurrentTask()
if (!task) throw new Error("No active task")
const checkpoint = {
id: `${task.task_id}_${task.checkpoints.length + 1}`,
commit_hash: commitHash,
description: options.description,
component: options.component,
changes: options.changes,
risks: options.risks,
expected_feedback: options.feedback,
timestamp: new Date().toISOString(),
}
await taskManager.addCheckpoint(checkpoint)
console.log(`Created checkpoint: ${checkpoint.id}`)
} catch (error) {
console.error("Failed to create checkpoint:", error)
process.exit(1)
}
})
program
.command("decide <index>")
.description("Resolve a pending decision")
.action(async (index: string) => {
try {
const idx = parseInt(index, 10) - 1
await taskManager.resolvePendingDecision(idx)
console.log("Decision resolved")
} catch (error) {
console.error("Failed to resolve decision:", error)
process.exit(1)
}
})
program.parse()

View file

@ -0,0 +1,24 @@
{
"task_id": "PM-CLEANUP-20250412",
"active_checkpoint": "",
"status": "in_progress",
"created_at": "2025-04-12T23:57:44.419Z",
"last_accessed": "2025-04-12T23:57:44.420Z",
"description": "Remove unused YamlParser implementation",
"initial_commit": "4417886324a54ad5c058813474b8a57a9859bba0",
"checkpoints": [],
"test_results": {
"unit_tests": {
"passing": 0,
"failing": 0,
"pending": 0
},
"linting": "",
"manual_testing": ""
},
"pending_decisions": [],
"rollback_info": {
"full_rollback": "git reset --hard 4417886324a54ad5c058813474b8a57a9859bba0",
"partial_rollbacks": {}
}
}

View file

@ -0,0 +1,63 @@
{
"task_id": "PM-STATE-FIX-20250412",
"active_checkpoint": "pm_state_fix_20250412_3",
"status": "in_progress",
"created_at": "2025-04-12T15:44:13-07:00",
"last_accessed": "2025-04-12T15:50:47-07:00",
"description": "Fix package manager state management and refresh issues",
"initial_commit": "4417886324a54ad5c058813474b8a57a9859bba0",
"checkpoints": [
{
"id": "pm_state_fix_20250412_1",
"commit_hash": "4417886324a54ad5c058813474b8a57a9859bba0",
"description": "UI State Management",
"component": "webview-ui/src/components/package-manager/PackageManagerView.tsx",
"changes": ["Removed premature item clearing", "Fixed state update timing"],
"risks": ["Race conditions between state updates", "Stale data display during refresh"],
"expected_feedback": [
"Items disappear and reappear during refresh",
"Refresh button gets stuck spinning",
"Old items shown after source changes"
],
"timestamp": "2025-04-12T15:44:13-07:00"
},
{
"id": "pm_state_fix_20250412_2",
"commit_hash": "4417886324a54ad5c058813474b8a57a9859bba0",
"description": "Error Handling",
"component": "webview-ui/src/components/package-manager/PackageManagerView.tsx",
"changes": ["Removed client-side error messages", "Improved timeout handling"],
"risks": ["Missing error feedback", "Timeout state confusion"],
"expected_feedback": ["No error message shown on failure", "UI stuck in loading state"],
"timestamp": "2025-04-12T15:45:00-07:00"
},
{
"id": "pm_state_fix_20250412_3",
"commit_hash": "4417886324a54ad5c058813474b8a57a9859bba0",
"description": "State Reset Logic",
"component": "webview-ui/src/components/package-manager/PackageManagerView.tsx",
"changes": ["Always update items on state change", "Proper timeout cleanup"],
"risks": ["Memory leaks from timeouts", "Inconsistent state after tab switch"],
"expected_feedback": ["Items don't update after source changes", "Refresh button state incorrect"],
"timestamp": "2025-04-12T15:45:30-07:00"
}
],
"test_results": {
"unit_tests": {
"passing": 1263,
"failing": 0,
"pending": 23
},
"linting": "No errors",
"manual_testing": "Confirmed working by user"
},
"pending_decisions": [],
"rollback_info": {
"full_rollback": "git reset --hard 4417886324a54ad5c058813474b8a57a9859bba0",
"partial_rollbacks": {
"ui_state": "git checkout pm_state_fix_20250412_1",
"error_handling": "git checkout pm_state_fix_20250412_2",
"state_reset": "git checkout pm_state_fix_20250412_3"
}
}
}

View file

@ -0,0 +1,56 @@
{
"task_id": "4d3f7a2e-8c1b-4f9d-b5e2-9c1d8f3a6b4d",
"description": "Remove unused YamlParser implementation",
"created_at": "2025-04-12T17:07:22-07:00",
"checkpoints": [
{
"id": "checkpoint_1",
"description": "Initial analysis of YamlParser removal",
"findings": [
"YamlParser.ts exists but is not imported anywhere",
"No test files are using it",
"No dynamic imports found"
],
"proposed_changes": [
"Remove src/services/package-manager/YamlParser.ts",
"Remove any associated test files"
],
"risks": [
"Might be used by dynamic imports",
"Could be referenced in package.json",
"Might be part of public API"
],
"expected_feedback": [
"Build errors after removal",
"Runtime errors in yaml parsing",
"Missing exports errors"
]
},
{
"id": "checkpoint_2",
"description": "Removal of YamlParser files",
"completed_at": "2025-04-12T17:25:29-07:00",
"changes_made": [
"Removed src/services/package-manager/YamlParser.ts",
"Verified no test file existed to remove"
],
"verification_steps": ["Confirmed file deletion", "Confirmed no test file present"]
},
{
"id": "checkpoint_3",
"description": "Test verification",
"completed_at": "2025-04-12T17:27:39-07:00",
"test_results": {
"total": 1286,
"passing": 1263,
"failing": 0,
"pending": 23
},
"conclusion": "No regressions detected after removing YamlParser.ts"
}
],
"current_state": {
"status": "completed",
"summary": "Successfully removed unused YamlParser.ts with no regressions"
}
}

161
.roo/task-manager.ts Normal file
View file

@ -0,0 +1,161 @@
import * as fs from "fs/promises"
import * as path from "path"
interface TaskCheckpoint {
id: string
commit_hash: string
description: string
component: string
changes: string[]
risks: string[]
expected_feedback: string[]
timestamp: string
}
interface TaskContext {
task_id: string
active_checkpoint: string
status: "in_progress" | "completed" | "failed"
created_at: string
last_accessed: string
description: string
initial_commit: string
checkpoints: TaskCheckpoint[]
test_results: {
unit_tests: {
passing: number
failing: number
pending: number
}
linting: string
manual_testing: string
}
pending_decisions: string[]
rollback_info: {
full_rollback: string
partial_rollbacks: Record<string, string>
}
}
class TaskManager {
private currentTaskId: string | null = null
private currentContext: TaskContext | null = null
private readonly logsDir = path.join(".roo", "prepare_logs")
constructor() {
this.ensureLogsDirectory()
}
private async ensureLogsDirectory() {
try {
await fs.mkdir(this.logsDir, { recursive: true })
} catch (error) {
console.error("Failed to create logs directory:", error)
}
}
async switchTask(taskId: string): Promise<void> {
if (this.currentTaskId) {
await this.saveCurrentTask()
}
await this.loadTask(taskId)
}
private async saveCurrentTask(): Promise<void> {
if (!this.currentTaskId || !this.currentContext) return
this.currentContext.last_accessed = new Date().toISOString()
const logPath = this.getLogPath(this.currentTaskId)
await fs.writeFile(logPath, JSON.stringify(this.currentContext, null, 2), "utf-8")
}
private async loadTask(taskId: string): Promise<void> {
const logPath = this.getLogPath(taskId)
try {
const content = await fs.readFile(logPath, "utf-8")
this.currentContext = JSON.parse(content)
this.currentTaskId = taskId
} catch (error) {
console.error(`Failed to load task ${taskId}:`, error)
throw error
}
}
async createTask(taskId: string, description: string, initialCommit: string): Promise<void> {
const newTask: TaskContext = {
task_id: taskId,
active_checkpoint: "",
status: "in_progress",
created_at: new Date().toISOString(),
last_accessed: new Date().toISOString(),
description,
initial_commit: initialCommit,
checkpoints: [],
test_results: {
unit_tests: { passing: 0, failing: 0, pending: 0 },
linting: "",
manual_testing: "",
},
pending_decisions: [],
rollback_info: {
full_rollback: `git reset --hard ${initialCommit}`,
partial_rollbacks: {},
},
}
const logPath = this.getLogPath(taskId)
await fs.writeFile(logPath, JSON.stringify(newTask, null, 2), "utf-8")
this.currentTaskId = taskId
this.currentContext = newTask
}
async addCheckpoint(checkpoint: TaskCheckpoint): Promise<void> {
if (!this.currentContext) throw new Error("No active task")
this.currentContext.checkpoints.push(checkpoint)
this.currentContext.active_checkpoint = checkpoint.id
this.currentContext.rollback_info.partial_rollbacks[checkpoint.description.toLowerCase().replace(/\s+/g, "_")] =
`git checkout ${checkpoint.commit_hash}`
await this.saveCurrentTask()
}
async updateTestResults(results: TaskContext["test_results"]): Promise<void> {
if (!this.currentContext) throw new Error("No active task")
this.currentContext.test_results = results
await this.saveCurrentTask()
}
async addPendingDecision(decision: string): Promise<void> {
if (!this.currentContext) throw new Error("No active task")
this.currentContext.pending_decisions.push(decision)
await this.saveCurrentTask()
}
async resolvePendingDecision(index: number): Promise<void> {
if (!this.currentContext) throw new Error("No active task")
this.currentContext.pending_decisions.splice(index, 1)
await this.saveCurrentTask()
}
getCurrentTask(): TaskContext | null {
return this.currentContext
}
private getLogPath(taskId: string): string {
return path.join(this.logsDir, `${taskId}.json`)
}
async listTasks(): Promise<string[]> {
const files = await fs.readdir(this.logsDir)
return files.filter((file) => file.endsWith(".json")).map((file) => file.replace(".json", ""))
}
}
export const taskManager = new TaskManager()

15
.roo/tsconfig.json Normal file
View file

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": ".",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["*.ts"],
"exclude": ["node_modules", "dist"]
}

View file

@ -1,140 +0,0 @@
import { XMLParser } from "fast-xml-parser"
import { validateAnyMetadata } from "./schemas"
/**
* Utility class for parsing and validating YAML content
*/
export class YamlParser {
private static parser = new XMLParser({
ignoreAttributes: false,
parseAttributeValue: true,
parseTagValue: true,
trimValues: true,
preserveOrder: true,
})
/**
* Parse YAML content into an object and validate against schema
* @param content YAML content to parse
* @param validate Whether to validate against schema (default: true)
* @returns Parsed and validated object
* @throws Error if parsing or validation fails
*/
static parse<T>(content: string, validate: boolean = true): T {
if (!content.trim()) {
return {} as T
}
try {
// Remove comments
const noComments = content.replace(/#[^\n]*/g, "")
// Handle multi-line strings
const processedContent = this.processMultilineStrings(noComments)
// Convert YAML to JSON-like structure
const jsonContent = processedContent
// Handle arrays with proper indentation
.replace(/^(\s*)-\s+(?=\S)/gm, (match, indent) => `${indent}array_item: `)
// Handle quoted strings
.replace(/^(\s*)([^:\n]+):\s*(['"])(.*?)\3\s*$/gm, (_, indent, key, quote, value) => {
const safeKey = this.sanitizeKey(key)
return `${indent}${safeKey}: ${value}`
})
// Handle unquoted key-value pairs
.replace(/^(\s*)([^:\n]+):\s*([^\n]*)$/gm, (_, indent, key, value) => {
const safeKey = this.sanitizeKey(key)
return `${indent}${safeKey}: ${value.trim()}`
})
// Parse as XML-like structure
const parsed = this.parser.parse(`<root>${jsonContent}</root>`)
// Convert array_item markers back to arrays and process nested structures
const result = this.processStructure(parsed.root || {})
// Validate against schema if requested
if (validate) {
return validateAnyMetadata(result) as T
} else {
return result as T
}
} catch (error) {
console.error("Failed to parse YAML:", error)
throw new Error(`Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`)
}
}
/**
* Process multi-line strings in YAML content
* @param content YAML content
* @returns Processed content
*/
private static processMultilineStrings(content: string): string {
return content.replace(/^(\s*[^:\n]+):\s*\|\s*\n((?:\s+[^\n]*\n?)*)/gm, (_, key, value) => {
const indentLevel = value.match(/^\s+/)?.[0].length || 0
const processedValue = value
.split("\n")
.map((line: string) => line.slice(indentLevel))
.join("\n")
.trim()
return `${key}: "${processedValue.replace(/"/g, '\\"')}"`
})
}
/**
* Sanitize YAML key for XML compatibility
* @param key Key to sanitize
* @returns Sanitized key
*/
private static sanitizeKey(key: string): string {
return key
.trim()
.replace(/[^\w-]/g, "_")
.replace(/^(\d)/, "_$1") // Prefix numbers with underscore
}
/**
* Process nested structures and arrays
* @param obj Object to process
* @returns Processed object
*/
private static processStructure(obj: any): any {
if (typeof obj !== "object" || obj === null) {
return obj
}
if (Array.isArray(obj)) {
return obj.map((item) => this.processStructure(item))
}
const result: any = {}
const arrays: { [key: string]: any[] } = {}
// First pass: collect array items
for (const [key, value] of Object.entries(obj)) {
if (key === "array_item") {
return this.processStructure(value)
}
const match = key.match(/^(.+?)_(\d+)$/)
if (match) {
const [, baseKey, index] = match
if (!arrays[baseKey]) {
arrays[baseKey] = []
}
arrays[baseKey][parseInt(index)] = this.processStructure(value)
continue
}
result[key] = this.processStructure(value)
}
// Second pass: merge arrays into result
for (const [key, value] of Object.entries(arrays)) {
result[key] = value.filter((item) => item !== undefined)
}
return result
}
}

View file

@ -175,8 +175,7 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
clearTimeout(fetchTimeoutRef.current)
}
// Clear items immediately when fetching starts
setItems([])
// Only set fetching state, don't clear items
setIsFetching(true)
try {
@ -189,13 +188,10 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
fetchTimeoutRef.current = setTimeout(() => {
console.log("Fetch timeout reached, resetting state")
setIsFetching(false)
setItems([]) // Clear items on timeout
vscode.window.showErrorMessage("Package manager items fetch timed out. Please try again.")
}, 30000) // 30 second timeout to match server timeout
} catch (error) {
console.error("Failed to fetch package manager items:", error)
setIsFetching(false)
setItems([]) // Clear items on error
}
}, [])
@ -222,7 +218,6 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
clearTimeout(fetchTimeoutRef.current)
}
setIsFetching(false)
setItems([]) // Clear items on error
} else {
// This is a refresh request
fetchPackageManagerItems()
@ -231,8 +226,6 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
if (message.type === "repositoryRefreshComplete" && message.url) {
setRefreshingUrls((prev) => prev.filter((url) => url !== message.url))
// Trigger a fetch to update items after refresh
fetchPackageManagerItems()
}
if (message.type === "state" && message.state?.packageManagerItems !== undefined) {
@ -243,6 +236,8 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
const receivedItems = message.state.packageManagerItems || []
console.log("Received package manager items:", receivedItems.length)
// Always update items, even if empty
setItems([...receivedItems])
setIsFetching(false)
}