Skip to content

docs(workflow): refine codex plan-execute prompt#256

Merged
Whiteks1 merged 5 commits intomainfrom
codex/codex-workflow-prompt
Apr 4, 2026
Merged

docs(workflow): refine codex plan-execute prompt#256
Whiteks1 merged 5 commits intomainfrom
codex/codex-workflow-prompt

Conversation

@Whiteks1
Copy link
Copy Markdown
Owner

@Whiteks1 Whiteks1 commented Apr 4, 2026

Summary

Refine the Codex workflow prompt into a clearer two-phase plan/execute template and align the quick-reference cheatsheet with the canonical prompt.

Scope

Documentation only.

Files:

  • .agents/prompts/codex-master-prompt.md
  • .agents/cursor-codex-cheatsheet.md
  • AGENTS.md

Validation

  • git diff --check

Notes

  • The prompt now makes the Cursor/Codex split explicit: Cursor scopes and reviews, Codex implements and validates.
  • The cheatsheet points to the canonical master prompt instead of duplicating long-form content.
  • AGENTS.md now links to the canonical workflow prompt and cheatsheet.
  • No runtime or trading behavior changed.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 4, 2026

Reviewer's Guide

Adds new MCP tools and helpers for safely listing and reading text artifacts under the outputs/ directory, and refines the Codex workflow documentation into a clearer two-phase plan/execute model aligned with a new Cursor+Codex cheatsheet.

Sequence diagram for quantlab_artifact_read MCP tool

sequenceDiagram
  participant Client
  participant MCPServer
  participant OutputsHelper
  participant FileSystem

  Client->>MCPServer: invoke quantlab_artifact_read(relative_path)
  MCPServer->>OutputsHelper: readOutputsArtifact(relative_path)
  OutputsHelper->>OutputsHelper: resolveOutputsPath(relative_path)
  OutputsHelper->>FileSystem: stat(resolvedPath)
  FileSystem-->>OutputsHelper: FileStats
  alt Path_is_directory
    OutputsHelper-->>MCPServer: throw Error("Expected a file under outputs")
    MCPServer-->>Client: isError response with failure message
  else Path_is_file
    OutputsHelper->>OutputsHelper: check BINARY_ARTIFACT_EXTENSIONS
    alt Is_binary_extension
      OutputsHelper-->>MCPServer: throw Error("Binary artifact reading is not supported")
      MCPServer-->>Client: isError response with failure message
    else Is_text_file
      OutputsHelper->>FileSystem: readFile(resolvedPath, utf8)
      FileSystem-->>OutputsHelper: content
      OutputsHelper->>OutputsHelper: truncateText(content)
      OutputsHelper-->>MCPServer: payload { root, requested_path, absolute_path, bytes, modified_at, truncated, content }
      MCPServer->>MCPServer: formatBytes(payload.bytes)
      MCPServer-->>Client: text content with metadata and body
    end
  end
Loading

Sequence diagram for quantlab_outputs_list MCP tool

sequenceDiagram
  participant Client
  participant MCPServer
  participant OutputsHelper
  participant FileSystem

  Client->>MCPServer: invoke quantlab_outputs_list(relative_path?)
  MCPServer->>OutputsHelper: listOutputs(relative_path)
  OutputsHelper->>OutputsHelper: resolveOutputsPath(relative_path)
  OutputsHelper->>FileSystem: stat(targetPath)
  FileSystem-->>OutputsHelper: DirStats
  alt Not_a_directory
    OutputsHelper-->>MCPServer: throw Error("Not a directory under outputs")
    MCPServer-->>Client: isError response with failure message
  else Is_directory
    OutputsHelper->>FileSystem: readdir(targetPath, withFileTypes)
    FileSystem-->>OutputsHelper: DirEntries
    loop For_each_entry
      OutputsHelper->>FileSystem: stat(entryPath)
      FileSystem-->>OutputsHelper: EntryStats
      OutputsHelper->>OutputsHelper: formatBytes(size)
      OutputsHelper->>OutputsHelper: toIsoString(mtime)
      OutputsHelper->>OutputsHelper: push entry metadata into entries list
    end
    OutputsHelper->>OutputsHelper: sort entries (directories first, then name)
    OutputsHelper-->>MCPServer: payload { root, requested_path, absolute_path, entry_count, entries }
    MCPServer-->>Client: JSON text listing outputs entries
  end
Loading

File-Level Changes

Change Details Files
Introduce secure outputs/ artifact listing and reading utilities as MCP tools.
  • Define OUTPUTS_ROOT and a set of binary file extensions to guard against unsupported artifact reads.
  • Add helpers to resolve and validate paths under outputs/, format byte sizes, and normalize timestamps to ISO strings.
  • Implement listOutputs() to return structured directory metadata for entries under outputs/.
  • Implement readOutputsArtifact() to safely read and truncate text files under outputs/, rejecting directories and known binary types.
  • Register quantlab_outputs_list and quantlab_artifact_read MCP tools that expose these helpers and return text responses with JSON or human-readable summaries, including error handling.
desktop/mcp-server.mjs
Document the new MCP tools in the desktop server README.
  • Extend the list of available tools to include quantlab_outputs_list and quantlab_artifact_read.
desktop/README.md
Refine the Codex master prompt into an explicit two-phase plan/execute workflow with validation and PR-shape guidance.
  • Reframe initial guidance from repository-context focus to task-focused source-of-truth files, adding the cheatsheet to the required reading list.
  • Introduce explicit operating constraints and current QuantLab priorities to shape Codex behavior and scope control.
  • Define a detailed two-phase workflow (Plan then Execute) with rules on scope, files, validation, and commit practices.
  • Add a validation matrix and recommended MCP validations, plus guidance on PR structure and response shapes for planning vs finishing implementation.
.agents/prompts/codex-master-prompt.md
Add a Cursor+Codex workflow cheatsheet that points to the canonical Codex prompt and standardizes the two-phase flow.
  • Create a Spanish-language quick-reference cheatsheet describing roles of Cursor and Codex and when to use each.
  • Embed canonical prompts for Cursor analysis, Codex planning, Codex execution, and Cursor final review, aligned with the master prompt.
  • Include the validation matrix, recommended usage patterns by change type, and branching/PR rules consistent with the Codex prompt.
.agents/cursor-codex-cheatsheet.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues, and left some high level feedback:

  • The binary-artifact guard in readOutputsArtifact relies solely on file extensions, which is easy to bypass; consider either inspecting file content (e.g., checking for non-text bytes) or enforcing a size/heuristic limit to avoid trying to render large/binary artifacts as UTF‑8.
  • The MCP tools quantlab_outputs_list and quantlab_artifact_read currently stringify their payloads into a single text block; if you expect other tools/agents to consume these programmatically, consider returning a more structured, machine-friendly format (e.g., JSON content type or a consistent schema) instead of preformatted text.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The binary-artifact guard in `readOutputsArtifact` relies solely on file extensions, which is easy to bypass; consider either inspecting file content (e.g., checking for non-text bytes) or enforcing a size/heuristic limit to avoid trying to render large/binary artifacts as UTF‑8.
- The MCP tools `quantlab_outputs_list` and `quantlab_artifact_read` currently stringify their payloads into a single text block; if you expect other tools/agents to consume these programmatically, consider returning a more structured, machine-friendly format (e.g., JSON content type or a consistent schema) instead of preformatted text.

## Individual Comments

### Comment 1
<location path="desktop/mcp-server.mjs" line_range="145-151" />
<code_context>
+  return {
+    root: "outputs",
+    requested_path: relativePath || ".",
+    absolute_path: targetPath,
+    entry_count: detailed.length,
+    entries: detailed,
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Avoid exposing absolute filesystem paths in the outputs API payloads.

The `absolute_path` field exposes internal filesystem layout, which can be sensitive if clients are untrusted or logs are shared. Consider removing this field or returning only a path relative to `outputs/` (since `relative_path`/`requested_path` already cover client needs) and keep absolute paths internal to the server.

```suggestion
  return {
    root: "outputs",
    requested_path: relativePath || ".",
    entry_count: detailed.length,
    entries: detailed,
  };
```
</issue_to_address>

### Comment 2
<location path="desktop/mcp-server.mjs" line_range="16-24" />
<code_context>
+const OUTPUTS_ROOT = path.resolve(PROJECT_ROOT, "outputs");
 const PYTHON_EXECUTABLE = process.env.QUANTLAB_PYTHON || "python";
 const MAX_OUTPUT_CHARS = 12000;
+const BINARY_ARTIFACT_EXTENSIONS = new Set([
+  ".png",
+  ".jpg",
+  ".jpeg",
+  ".gif",
+  ".webp",
+  ".bmp",
+  ".ico",
+  ".pdf",
+]);

</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider an allowlist of text types instead of a blocklist of binary extensions.

This blocklist only covers a few binary types; others (e.g. `.zip`, `.tar`, `.bin`, custom extensions) could still be treated as UTF‑8, causing noisy or failing reads. Since this tool is for text artifacts, consider switching to an allowlist of known text formats (e.g. `.txt`, `.md`, `.log`, `.json`) and treating everything else as binary by default.

Suggested implementation:

```javascript
const OUTPUTS_ROOT = path.resolve(PROJECT_ROOT, "outputs");
const PYTHON_EXECUTABLE = process.env.QUANTLAB_PYTHON || "python";
const MAX_OUTPUT_CHARS = 12000;

// Only these extensions will be treated as text artifacts.
// Everything else should be assumed to be binary by default.
const TEXT_ARTIFACT_EXTENSIONS = new Set([
  ".txt",
  ".md",
  ".markdown",
  ".log",
  ".json",
  ".jsonl",
  ".csv",
  ".tsv",
  ".yaml",
  ".yml",
  ".xml",
  ".html",
  ".htm",
]);

```

Anywhere `BINARY_ARTIFACT_EXTENSIONS` is used, you'll need to:

1. Rename the reference to `TEXT_ARTIFACT_EXTENSIONS`.
2. Invert the logic so that text is explicitly allowed and everything else is treated as binary. For example:
   - If you currently do something like:
     ```js
     const ext = path.extname(filePath).toLowerCase();
     const isBinary = BINARY_ARTIFACT_EXTENSIONS.has(ext);
     if (!isBinary) {
       // treat as text
     }
     ```
     you should change it to:
     ```js
     const ext = path.extname(filePath).toLowerCase();
     const isText = TEXT_ARTIFACT_EXTENSIONS.has(ext);
     if (isText) {
       // treat as text
     } else {
       // treat as binary
     }
     ```
3. Remove any assumptions elsewhere that "unknown extension => text"; instead, default unknown/absent extensions to binary.
</issue_to_address>

### Comment 3
<location path="desktop/mcp-server.mjs" line_range="123-136" />
<code_context>
+    throw new Error(`Not a directory under outputs/: ${relativePath || "."}`);
+  }
+
+  const entries = await fs.readdir(targetPath, { withFileTypes: true });
+  const detailed = [];
+  for (const entry of entries) {
+    const entryPath = path.join(targetPath, entry.name);
+    const entryStat = await fs.stat(entryPath);
+    detailed.push({
+      name: entry.name,
</code_context>
<issue_to_address>
**suggestion (performance):** Parallelize per-entry `fs.stat` calls in `listOutputs` to reduce latency on large directories.

`fs.stat` is awaited sequentially in the loop, which can be slow for large directories. Consider collecting the `fs.stat` calls into a promise array and using `await Promise.all(...)`, then rebuilding `detailed` from the resolved results to maintain the current ordering.

```suggestion
  const entries = await fs.readdir(targetPath, { withFileTypes: true });

  // Parallelize fs.stat calls for all entries to reduce latency on large directories.
  const entryStats = await Promise.all(
    entries.map((entry) => {
      const entryPath = path.join(targetPath, entry.name);
      return fs.stat(entryPath);
    }),
  );

  const detailed = entries.map((entry, index) => {
    const entryPath = path.join(targetPath, entry.name);
    const entryStat = entryStats[index];

    return {
      name: entry.name,
      kind: entry.isDirectory() ? "directory" : "file",
      relative_path: path
        .relative(PROJECT_ROOT, entryPath)
        .replaceAll("\\", "/"),
      size_bytes: entry.isDirectory() ? null : entryStat.size,
      size_human: entry.isDirectory() ? null : formatBytes(entryStat.size),
      modified_at: toIsoString(entryStat.mtime),
    };
  });
```
</issue_to_address>

### Comment 4
<location path="desktop/mcp-server.mjs" line_range="32-39" />
<code_context>
   return `${text.slice(0, MAX_OUTPUT_CHARS)}\n...[truncated]`;
 }

+function resolveOutputsPath(relativePath) {
+  const requested = relativePath || "";
+  const resolvedPath = path.resolve(OUTPUTS_ROOT, requested);
+  const relative = path.relative(OUTPUTS_ROOT, resolvedPath);
+  if (relative.startsWith("..") || path.isAbsolute(relative)) {
+    throw new Error(`Refusing to access outside outputs/: ${relativePath}`);
+  }
+  return resolvedPath;
+}
+
</code_context>
<issue_to_address>
**🚨 question (security):** Decide whether symlinks under `outputs/` should be allowed to escape the tree.

The current checks block `..` and absolute paths, but they don’t prevent a symlink under `outputs/` from pointing elsewhere in the filesystem and being followed by `fs.stat` / `fs.readFile`. If access is meant to be strictly confined to `outputs/`, consider rejecting symlinks or validating `fs.realpath(resolvedPath)` to ensure it still lies under `OUTPUTS_ROOT`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +145 to +151
return {
root: "outputs",
requested_path: relativePath || ".",
absolute_path: targetPath,
entry_count: detailed.length,
entries: detailed,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 suggestion (security): Avoid exposing absolute filesystem paths in the outputs API payloads.

The absolute_path field exposes internal filesystem layout, which can be sensitive if clients are untrusted or logs are shared. Consider removing this field or returning only a path relative to outputs/ (since relative_path/requested_path already cover client needs) and keep absolute paths internal to the server.

Suggested change
return {
root: "outputs",
requested_path: relativePath || ".",
absolute_path: targetPath,
entry_count: detailed.length,
entries: detailed,
};
return {
root: "outputs",
requested_path: relativePath || ".",
entry_count: detailed.length,
entries: detailed,
};

Comment on lines +16 to +24
const BINARY_ARTIFACT_EXTENSIONS = new Set([
".png",
".jpg",
".jpeg",
".gif",
".webp",
".bmp",
".ico",
".pdf",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Consider an allowlist of text types instead of a blocklist of binary extensions.

This blocklist only covers a few binary types; others (e.g. .zip, .tar, .bin, custom extensions) could still be treated as UTF‑8, causing noisy or failing reads. Since this tool is for text artifacts, consider switching to an allowlist of known text formats (e.g. .txt, .md, .log, .json) and treating everything else as binary by default.

Suggested implementation:

const OUTPUTS_ROOT = path.resolve(PROJECT_ROOT, "outputs");
const PYTHON_EXECUTABLE = process.env.QUANTLAB_PYTHON || "python";
const MAX_OUTPUT_CHARS = 12000;

// Only these extensions will be treated as text artifacts.
// Everything else should be assumed to be binary by default.
const TEXT_ARTIFACT_EXTENSIONS = new Set([
  ".txt",
  ".md",
  ".markdown",
  ".log",
  ".json",
  ".jsonl",
  ".csv",
  ".tsv",
  ".yaml",
  ".yml",
  ".xml",
  ".html",
  ".htm",
]);

Anywhere BINARY_ARTIFACT_EXTENSIONS is used, you'll need to:

  1. Rename the reference to TEXT_ARTIFACT_EXTENSIONS.
  2. Invert the logic so that text is explicitly allowed and everything else is treated as binary. For example:
    • If you currently do something like:
      const ext = path.extname(filePath).toLowerCase();
      const isBinary = BINARY_ARTIFACT_EXTENSIONS.has(ext);
      if (!isBinary) {
        // treat as text
      }
      you should change it to:
      const ext = path.extname(filePath).toLowerCase();
      const isText = TEXT_ARTIFACT_EXTENSIONS.has(ext);
      if (isText) {
        // treat as text
      } else {
        // treat as binary
      }
  3. Remove any assumptions elsewhere that "unknown extension => text"; instead, default unknown/absent extensions to binary.

Comment on lines +123 to +136
const entries = await fs.readdir(targetPath, { withFileTypes: true });
const detailed = [];
for (const entry of entries) {
const entryPath = path.join(targetPath, entry.name);
const entryStat = await fs.stat(entryPath);
detailed.push({
name: entry.name,
kind: entry.isDirectory() ? "directory" : "file",
relative_path: path.relative(PROJECT_ROOT, entryPath).replaceAll("\\", "/"),
size_bytes: entry.isDirectory() ? null : entryStat.size,
size_human: entry.isDirectory() ? null : formatBytes(entryStat.size),
modified_at: toIsoString(entryStat.mtime),
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Parallelize per-entry fs.stat calls in listOutputs to reduce latency on large directories.

fs.stat is awaited sequentially in the loop, which can be slow for large directories. Consider collecting the fs.stat calls into a promise array and using await Promise.all(...), then rebuilding detailed from the resolved results to maintain the current ordering.

Suggested change
const entries = await fs.readdir(targetPath, { withFileTypes: true });
const detailed = [];
for (const entry of entries) {
const entryPath = path.join(targetPath, entry.name);
const entryStat = await fs.stat(entryPath);
detailed.push({
name: entry.name,
kind: entry.isDirectory() ? "directory" : "file",
relative_path: path.relative(PROJECT_ROOT, entryPath).replaceAll("\\", "/"),
size_bytes: entry.isDirectory() ? null : entryStat.size,
size_human: entry.isDirectory() ? null : formatBytes(entryStat.size),
modified_at: toIsoString(entryStat.mtime),
});
}
const entries = await fs.readdir(targetPath, { withFileTypes: true });
// Parallelize fs.stat calls for all entries to reduce latency on large directories.
const entryStats = await Promise.all(
entries.map((entry) => {
const entryPath = path.join(targetPath, entry.name);
return fs.stat(entryPath);
}),
);
const detailed = entries.map((entry, index) => {
const entryPath = path.join(targetPath, entry.name);
const entryStat = entryStats[index];
return {
name: entry.name,
kind: entry.isDirectory() ? "directory" : "file",
relative_path: path
.relative(PROJECT_ROOT, entryPath)
.replaceAll("\\", "/"),
size_bytes: entry.isDirectory() ? null : entryStat.size,
size_human: entry.isDirectory() ? null : formatBytes(entryStat.size),
modified_at: toIsoString(entryStat.mtime),
};
});

Comment on lines +32 to +39
function resolveOutputsPath(relativePath) {
const requested = relativePath || "";
const resolvedPath = path.resolve(OUTPUTS_ROOT, requested);
const relative = path.relative(OUTPUTS_ROOT, resolvedPath);
if (relative.startsWith("..") || path.isAbsolute(relative)) {
throw new Error(`Refusing to access outside outputs/: ${relativePath}`);
}
return resolvedPath;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 question (security): Decide whether symlinks under outputs/ should be allowed to escape the tree.

The current checks block .. and absolute paths, but they don’t prevent a symlink under outputs/ from pointing elsewhere in the filesystem and being followed by fs.stat / fs.readFile. If access is meant to be strictly confined to outputs/, consider rejecting symlinks or validating fs.realpath(resolvedPath) to ensure it still lies under OUTPUTS_ROOT.

@Whiteks1 Whiteks1 merged commit ef7acb3 into main Apr 4, 2026
2 checks passed
@Whiteks1 Whiteks1 deleted the codex/codex-workflow-prompt branch April 4, 2026 18:58
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