-
Notifications
You must be signed in to change notification settings - Fork 344
stack1:Tfeat: harden config runtime loading and utility flow #160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ca5a433
edb1f41
c4e7331
9d4c729
ccb2a12
bf4d7a7
e609784
1f2b42b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| export * from "./types.ts"; | ||
| export * from "./loader.ts"; | ||
| export * from "./detector.ts"; | ||
| export * from "./loader.ts"; | ||
| export * from "./types.ts"; | ||
| export * from "./writer.ts"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; | ||
| import { appendFile } from "node:fs/promises"; | ||
| import YAML from "yaml"; | ||
| import { logWarn } from "../ui/logger.ts"; | ||
| import { detectProject } from "./detector.ts"; | ||
| import { getConfigPath, getProgressPath, getRalphyDir } from "./loader.ts"; | ||
| import type { RalphyConfig } from "./types.ts"; | ||
|
|
@@ -35,7 +36,7 @@ rules: | |
| # - "Use server actions instead of API routes in Next.js" | ||
| # | ||
| # Skills/playbooks (optional): | ||
| # - "Before coding, read and follow any relevant skill/playbook docs under .opencode/skills, .claude/skills, or .github/skills." | ||
| # - "Before coding, read and follow any relevant skill/playbook docs under .opencode/skills or .claude/skills." | ||
|
|
||
| # Boundaries - files/folders the AI should not modify | ||
| boundaries: | ||
|
|
@@ -49,9 +50,17 @@ boundaries: | |
|
|
||
| /** | ||
| * Escape a value for safe YAML string | ||
| * BUG FIX: Use YAML library for proper escaping to prevent injection attacks | ||
| */ | ||
| function escapeYaml(value: string | undefined | null): string { | ||
| return (value || "").replace(/"/g, '\\"'); | ||
| if (!value) return ""; | ||
| // Keep the value safe inside an existing double-quoted scalar. | ||
| // This prevents quote breakout and newline-based injection. | ||
| return value | ||
| .replace(/\\/g, "\\\\") | ||
| .replace(/"/g, '\\"') | ||
| .replace(/\r/g, "\\r") | ||
| .replace(/\n/g, "\\n"); | ||
| } | ||
|
Comment on lines
55
to
64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High
function escapeYaml(value: string | undefined | null): string {
if (!value) return "";
- // Use YAML library for proper escaping instead of simple quote replacement
- // This prevents YAML injection attacks
- const serialized = YAML.stringify(value).trim();
- // Remove surrounding quotes added by YAML.stringify for simple strings
- return serialized.replace(/^"|"$/g, "");
+ // Escape backslashes and double quotes, then wrap in double quotes
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
+ return `"${escaped}"`;
}🚀 Reply "fix it for me" or copy this AI Prompt for your agent: |
||
|
|
||
| /** | ||
|
|
@@ -108,44 +117,8 @@ export function addRule(rule: string, workDir = process.cwd()): void { | |
| writeFileSync(configPath, YAML.stringify(parsed), "utf-8"); | ||
| } | ||
|
|
||
| /** Queue for batching progress writes */ | ||
| const progressWriteQueue: Map<string, string[]> = new Map(); | ||
| let flushTimeout: ReturnType<typeof setTimeout> | null = null; | ||
|
|
||
| /** | ||
| * Flush all pending progress writes to disk | ||
| */ | ||
| async function flushProgressWrites(): Promise<void> { | ||
| if (progressWriteQueue.size === 0) return; | ||
|
|
||
| const entries = [...progressWriteQueue.entries()]; | ||
| progressWriteQueue.clear(); | ||
| flushTimeout = null; | ||
|
|
||
| for (const [path, lines] of entries) { | ||
| try { | ||
| await appendFile(path, lines.join(""), "utf-8"); | ||
| } catch { | ||
| // Ignore write errors for progress logging | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Schedule a flush of progress writes (debounced) | ||
| */ | ||
| function scheduleFlush(): void { | ||
| if (flushTimeout) return; | ||
| flushTimeout = setTimeout(() => { | ||
| void flushProgressWrites(); | ||
| }, 100); // Batch writes within 100ms window | ||
| } | ||
|
|
||
| /** | ||
| * Log a task to the progress file (async, batched) | ||
| * | ||
| * Performance optimized: uses async I/O and batches writes within 100ms windows | ||
| * to reduce file system contention in parallel mode. | ||
| * Log a task to the progress file | ||
| */ | ||
| export function logTaskProgress( | ||
| task: string, | ||
|
|
@@ -162,23 +135,7 @@ export function logTaskProgress( | |
| const icon = status === "completed" ? "✓" : "✗"; | ||
| const line = `- [${icon}] ${timestamp} - ${task}\n`; | ||
|
|
||
| // Add to write queue | ||
| const existing = progressWriteQueue.get(progressPath) || []; | ||
| existing.push(line); | ||
| progressWriteQueue.set(progressPath, existing); | ||
|
|
||
| // Schedule async flush | ||
| scheduleFlush(); | ||
| } | ||
|
|
||
| /** | ||
| * Force flush all pending progress writes immediately | ||
| * Call this before process exit to ensure all writes are persisted | ||
| */ | ||
| export async function flushAllProgressWrites(): Promise<void> { | ||
| if (flushTimeout) { | ||
| clearTimeout(flushTimeout); | ||
| flushTimeout = null; | ||
| } | ||
| await flushProgressWrites(); | ||
| void appendFile(progressPath, line, "utf-8").catch((error) => { | ||
| logWarn(`Failed to append task progress: ${error}`); | ||
| }); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Medium
config/loader.ts:149Suggestion:
validateCommandforbids spaces, so multi-word commands inloadTestCommand,loadLintCommand, andloadBuildCommandare treated as invalid and reset to empty strings. Consider splitting the config intocommandandargsand validate only the executable, or relax the validation to allow spaces/common arg syntax to avoid rejecting valid commands.🚀 Reply "fix it for me" or copy this AI Prompt for your agent: