Skip to content

fix(ops): operator export scripts honor --help and refuse out-of-repo output paths - #36

Closed
nish3451 wants to merge 4 commits into
mainfrom
fix/operator-export-cli-help
Closed

fix(ops): operator export scripts honor --help and refuse out-of-repo output paths#36
nish3451 wants to merge 4 commits into
mainfrom
fix/operator-export-cli-help

Conversation

@nish3451

@nish3451 nish3451 commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

What

Closes the operator-export CLI safety gap: export scripts ignored --help and
accepted --output/--html/--ops/--loom-links paths that could write or
overwrite cockpits, missions, and other files anywhere on the machine.

Changes

  • New scripts/lib/operator-cli.mjs
    • handleHelp(args, usage) — prints usage and exits 0 before any work
      when --help / -h is present.
    • resolveOutputPath(value, {flag, fallback}) — resolves operator-supplied
      output paths against the service repository root and refuses paths that
      escape it via absolute paths, .. traversal, or symlink escapes.
  • All 21 npm-script-backed export scripts now call handleHelp first and
    route every output path through resolveOutputPath.
  • export-internal-dashboard.mjs also moves its parity scratch file from
    /tmp into gitignored runs/ (cleaned up after use), so the dashboard no
    longer writes outside the repository.
  • test-active-operator-surfaces.mjs extended:
    • every active export script must exit 0 on --help, print usage, and not
      overwrite live-metrics.md, the proof library, or the daily money mission;
    • escape probes: --output=../…, absolute /tmp/…, ../../ HTML path,
      --loom-links=../…, --ops=/tmp/…, and a symlink escape are all refused
      without creating the file.

Validation

  • npm run ci — full gate passes (all suites + node --check on every script).
  • Note: retired export scripts (no npm wiring, e.g. export-owned-*) are not
    part of the active operator surface and were left untouched.

Files changed

  • 22 scripts under scripts/ (21 export scripts + extended surface test)
  • new scripts/lib/operator-cli.mjs

Summary by CodeRabbit

  • New Features

    • Added consistent --help and -h guidance across export commands.
    • Added support for safe, validated output paths within the service repository.
    • Added clear usage information for supported command-line options.
  • Bug Fixes

    • Prevented exports from writing files outside the service repository, including through symlinks.
    • Improved output handling for Markdown, HTML, JSON, and supporting files.
    • Ensured help commands do not modify generated artifacts.

…-repo output paths

Every active operator export script now handles --help/-h before doing any
work: it prints usage and exits 0 without writing or overwriting any cockpit
or mission artifact. Output paths (--output, --html, --ops, --loom-links)
are resolved against the service repository root and refused when they
escape it via absolute paths, .. traversal, or symlinks, so an export can
no longer write or overwrite files anywhere on the machine.

- Add scripts/lib/operator-cli.mjs with handleHelp() and resolveOutputPath()
- Wire both into all 21 npm-script-backed export scripts
- Route the internal-dashboard parity scratch file inside the repo (runs/)
  instead of /tmp, still cleaned up after use
- Extend test-active-operator-surfaces.mjs: --help surface for every active
  export script (exit 0 + usage + no overwrite) and escape-refusal probes
  for absolute, ..-relative, and symlink-escape paths

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds shared CLI help and output-path validation. Export scripts use these helpers for generated files and related inputs. Tests cover help behavior, artifact preservation, traversal, absolute paths, and symlink escapes.

Changes

Operator CLI safety

Layer / File(s) Summary
Shared CLI validation
scripts/lib/operator-cli.mjs
Adds handleHelp and resolveOutputPath with repository-boundary and symlink checks.
Exporter CLI and output integration
scripts/export-*.mjs
Adds usage handling and routes export writes and selected reads through validated paths. The internal dashboard also uses a repository-local scratch file.
Operator surface validation
scripts/test-active-operator-surfaces.mjs
Tests help output, successful exits, unchanged artifacts, rejected escaping paths, and blocked outside-root writes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: adding help handling and rejecting output paths outside the repository.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/operator-export-cli-help

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a568523ca5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +43 to +44
let existing = resolved;
while (!existsSync(existing)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject dangling symlinks before returning the output path

When the requested output itself is a symlink to a nonexistent file outside the repository, existsSync(existing) returns false, so this loop skips the symlink and validates only its in-repository parent. The subsequent writeFileSync follows the dangling symlink and creates the external target; for example, a repository symlink report.md -> /tmp/external-report.md lets --output=report.md write outside the repository while exiting successfully. Inspect symlink components with lstatSync/readlinkSync, including dangling links, rather than relying on existsSync.

Useful? React with 👍 / 👎.

Comment on lines +143 to +144
for (const [script, args, escapePath] of escapeProbes) {
rmSync(escapePath, {force: true})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep escape probes inside test-owned temporary storage

When a developer runs npm test, this unconditional cleanup deletes predictable paths outside the test directory before any assertion runs. Because T is created directly under /tmp, the current probes resolve to locations such as /tmp/escape-metrics.md, /tmp/escape-cockpit.html, and even /escape-mission.html; any pre-existing user or process-owned files at those paths are silently removed. Create a dedicated external temporary directory owned by this test and target all escape probes within it instead.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/export-followup-cockpit.mjs`:
- Around line 408-411: Update the output-directory handling around
resolvedOutputPath to import dirname from node:path and derive outputDir with
dirname(resolvedOutputPath) instead of splitting on "/". Keep the existing
recursive mkdirSync and writeFileSync behavior unchanged.

In `@scripts/export-growth-cockpit.mjs`:
- Around line 246-249: Replace the manual output-directory extraction in the
export flow with dirname(resolvedOutputPath) from node:path, ensuring Windows
backslash-separated paths are handled before mkdirSync creates the parent
directory.

In `@scripts/export-growth-doctor.mjs`:
- Around line 238-241: Update the output-directory handling around
resolvedOutputPath to import and use dirname from node:path instead of splitting
on "/". Pass dirname(resolvedOutputPath) to mkdirSync with recursive creation
before writeFileSync, preserving the existing resolved output path.

In `@scripts/export-growth-metrics.mjs`:
- Around line 187-190: Update the output-directory calculation around
resolvedOutputPath to import dirname from node:path and call
dirname(resolvedOutputPath) instead of splitting on "/"; retain the existing
recursive mkdirSync and writeFileSync flow.

In `@scripts/export-internal-dashboard.mjs`:
- Around line 59-61: Wrap the parity command and cleanup in a try/finally block
so parityScratchPath is removed even when runJson throws. Keep the existing
runJson invocation and rmSync cleanup behavior, with cleanup executing in the
finally block.

In `@scripts/export-lead-scoring-cockpit.mjs`:
- Around line 300-303: Replace the manual output-directory derivation in the
resolvedOutputPath flow with dirname(resolvedOutputPath) from node:path, and use
that result for the existing mkdirSync call so native Windows and POSIX path
separators are handled correctly.

In `@scripts/export-market-proof-run.mjs`:
- Around line 341-352: Replace the manual slash-based parent-directory
extraction for resolvedOutputPath and resolvedLoomLinksPath with dirname() from
node:path. Use the returned directories in the existing mkdirSync calls so both
output files create their parent directories correctly on all platforms.

In `@scripts/export-proof-library.mjs`:
- Around line 68-71: Update the resolved output directory logic near
resolvedOutputPath to import and use dirname from node:path instead of splitting
on "/". Keep the existing conditional mkdirSync behavior and writeFileSync flow
unchanged.

In `@scripts/export-prospect-outbox.mjs`:
- Around line 647-650: Update the output-directory calculation near
resolvedOutputPath to import and use dirname() from node:path instead of
splitting on "/"; retain the conditional mkdirSync(outputDir, { recursive: true
}) before writeFileSync so Windows paths create their parent directory
correctly.

In `@scripts/export-recording-cockpit.mjs`:
- Around line 584-587: Replace the manual parent-directory extraction after
resolveOutputPath with dirname(resolvedOutputPath) from node:path, ensuring the
import is available. Keep the existing conditional mkdirSync behavior and
writeFileSync flow unchanged.

In `@scripts/test-active-operator-surfaces.mjs`:
- Around line 121-132: Extend the help-surface loop in the active-operator test
to run each script with both --help and -h. Apply the same successful-exit and
Usage: assertions to each flag, and ensure the existing live-metrics and
proof-library preservation checks cover both invocations; likewise validate
export-daily-money-mission.mjs with -h while preserving the daily mission
artifact.
- Around line 134-142: Update the operator export path validation exercised by
escapeProbes to reject any isAbsolute(raw) output path before resolving it,
including absolute paths that fall within the service root. Add an in-repository
absolute-path probe and preserve the existing rejection coverage for
parent-relative and symlink escapes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a891ba0-37c6-47dd-8d43-b23953be5f23

📥 Commits

Reviewing files that changed from the base of the PR and between e0769ca and a568523.

📒 Files selected for processing (23)
  • scripts/export-client-delivery-cockpit.mjs
  • scripts/export-daily-money-mission.mjs
  • scripts/export-followup-cockpit.mjs
  • scripts/export-growth-cockpit.mjs
  • scripts/export-growth-doctor.mjs
  • scripts/export-growth-metrics.mjs
  • scripts/export-internal-dashboard.mjs
  • scripts/export-lead-scoring-cockpit.mjs
  • scripts/export-managed-it-one-pager.mjs
  • scripts/export-market-benchmark.mjs
  • scripts/export-market-learning-review.mjs
  • scripts/export-market-proof-cockpit.mjs
  • scripts/export-market-proof-run.mjs
  • scripts/export-proof-library.mjs
  • scripts/export-prospect-outbox.mjs
  • scripts/export-recording-cockpit.mjs
  • scripts/export-recording-queue.mjs
  • scripts/export-recording-rehearsal-check.mjs
  • scripts/export-recording-teleprompter.mjs
  • scripts/export-sales-cockpit.mjs
  • scripts/export-sender-setup-guide.mjs
  • scripts/lib/operator-cli.mjs
  • scripts/test-active-operator-surfaces.mjs

Comment on lines +408 to +411
const resolvedOutputPath = resolveOutputPath(outputPath);
const outputDir = resolvedOutputPath.split("/").slice(0, -1).join("/");
if (outputDir) mkdirSync(outputDir, { recursive: true });
writeFileSync(outputPath, html);
writeFileSync(resolvedOutputPath, html);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all resolved paths that derive a parent directory by splitting on "/".
rg -n -C 2 'resolved(Output|LoomLinks)Path\.split\("/"\)' scripts

# Expected: use node:path dirname() for each resolved filesystem path.

Repository: nish3451/tinystudio-in

Length of output: 5843


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- imports and path helper references ---'
rg -n -C 5 'from "node:path"|resolveOutputPath|resolvedOutputPath|outputDir' scripts/export-followup-cockpit.mjs

printf '%s\n' '--- helper definitions across scripts ---'
rg -n -C 6 'function resolveOutputPath|const resolveOutputPath|resolveOutputPath =' scripts -g '*.mjs'

Repository: nish3451/tinystudio-in

Length of output: 2545


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- operator-cli path imports and helper ---'
cat -n scripts/lib/operator-cli.mjs | sed -n '1,60p'

printf '%s\n' '--- followup path-related imports and output setup ---'
cat -n scripts/export-followup-cockpit.mjs | sed -n '1,25p'
cat -n scripts/export-followup-cockpit.mjs | sed -n '400,415p'

Repository: nish3451/tinystudio-in

Length of output: 4901


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
const path = require('node:path');

const windowsPath = 'C:\\work\\exports\\followup.html';
const splitDir = windowsPath.split('/').slice(0, -1).join('/');
const dirnameResult = path.win32.dirname(windowsPath);

console.log(JSON.stringify({
  windowsPath,
  splitDir,
  splitDirIsEmpty: splitDir === '',
  dirnameResult,
  unixSplitDir: '/work/exports/followup.html'.split('/').slice(0, -1).join('/'),
  unixDirname: path.posix.dirname('/work/exports/followup.html'),
}, null, 2));
JS

Repository: nish3451/tinystudio-in

Length of output: 370


Use dirname() for the resolved output path.

On Windows, split("/") produces an empty outputDir, so mkdirSync() does not create missing parent directories. writeFileSync() then fails with ENOENT. Import dirname from node:path and use dirname(resolvedOutputPath).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/export-followup-cockpit.mjs` around lines 408 - 411, Update the
output-directory handling around resolvedOutputPath to import dirname from
node:path and derive outputDir with dirname(resolvedOutputPath) instead of
splitting on "/". Keep the existing recursive mkdirSync and writeFileSync
behavior unchanged.

Comment on lines +246 to +249
const resolvedOutputPath = resolveOutputPath(outputPath);
const outputDir = resolvedOutputPath.split("/").slice(0, -1).join("/");
if (outputDir) mkdirSync(outputDir, { recursive: true });
writeFileSync(outputPath, `${html.replace(/[ \t]+$/gm, "").trimEnd()}\n`);
writeFileSync(resolvedOutputPath, `${html.replace(/[ \t]+$/gm, "").trimEnd()}\n`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- file location and size ---'
fd -t f 'export-growth-cockpit\.mjs$' .
wc -l scripts/export-growth-cockpit.mjs
printf '%s\n' '--- imports and path handling ---'
sed -n '1,45p' scripts/export-growth-cockpit.mjs
sed -n '220,260p' scripts/export-growth-cockpit.mjs
printf '%s\n' '--- relevant symbol usages ---'
rg -n 'resolveOutputPath|outputPath|mkdirSync|writeFileSync|dirname|pathToFileURL' scripts/export-growth-cockpit.mjs

Repository: nish3451/tinystudio-in

Length of output: 3568


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path, PurePosixPath, PureWindowsPath

source = Path("scripts/export-growth-cockpit.mjs").read_text()
print("--- deterministic split behavior ---")
for value in [
    r"C:\reports\growth.html",
    r"C:/reports/growth.html",
    "/reports/growth.html",
    "growth.html",
]:
    split_parent = value.split("/")[:-1]
    split_dir = "/".join(split_parent)
    print(f"{value!r} -> split parent {split_dir!r}")

print("--- Windows path interpretation ---")
for value in [r"C:\reports\growth.html", r"C:/reports/growth.html"]:
    p = PureWindowsPath(value)
    print(f"{value!r} -> parent {str(p.parent)!r}, name {p.name!r}")
PY

Repository: nish3451/tinystudio-in

Length of output: 556


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- operator CLI path resolver ---'
fd -t f 'operator-cli\.mjs$' .
wc -l scripts/lib/operator-cli.mjs
cat -n scripts/lib/operator-cli.mjs
printf '%s\n' '--- related output-path tests and documentation ---'
rg -n -i 'resolveOutputPath|--output=|growth-cockpit|output path' scripts test tests README.md package.json 2>/dev/null || true

Repository: nish3451/tinystudio-in

Length of output: 27332


🏁 Script executed:

#!/bin/bash
set -e
node - <<'JS'
const path = require("node:path");

const root = "C:\\service";
const inputs = [
  "runs\\growth-cockpit.html",
  "nested\\new\\growth-cockpit.html",
  "runs/growth-cockpit.html",
  "C:\\service\\nested\\new\\growth-cockpit.html",
];

for (const raw of inputs) {
  const resolved = path.win32.resolve(root, raw);
  const currentDir = resolved.split("/").slice(0, -1).join("/");
  console.log(JSON.stringify({
    raw,
    resolved,
    currentDir,
    currentCreatesDirectory: Boolean(currentDir),
    dirname: path.win32.dirname(resolved),
  }));
}
JS

Repository: nish3451/tinystudio-in

Length of output: 878


Use dirname() for the resolved output path.

On Windows, resolveOutputPath() returns backslash-separated paths, so split("/") leaves outputDir empty. Nested output paths then fail when their parent directory does not exist.

Use dirname(resolvedOutputPath) from node:path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/export-growth-cockpit.mjs` around lines 246 - 249, Replace the manual
output-directory extraction in the export flow with dirname(resolvedOutputPath)
from node:path, ensuring Windows backslash-separated paths are handled before
mkdirSync creates the parent directory.

Comment on lines +238 to +241
const resolvedOutputPath = resolveOutputPath(outputPath);
const outputDir = resolvedOutputPath.split("/").slice(0, -1).join("/");
if (outputDir) mkdirSync(outputDir, { recursive: true });
writeFileSync(outputPath, markdown);
writeFileSync(resolvedOutputPath, markdown);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
file="scripts/export-growth-doctor.mjs"
printf '%s\n' "== imports and target code =="
sed -n '1,45p' "$file"
sed -n '220,250p' "$file"
printf '%s\n' "== path-related usage =="
rg -n "resolveOutputPath|dirname|outputDir|mkdirSync|writeFileSync" "$file"

Repository: nish3451/tinystudio-in

Length of output: 3462


🏁 Script executed:

#!/bin/bash
set -e
file="scripts/lib/operator-cli.mjs"
printf '%s\n' "== resolver definition =="
rg -n -A35 -B10 "function resolveOutputPath|const resolveOutputPath|export .*resolveOutputPath" "$file"
printf '%s\n' "== resolver callers =="
rg -n "resolveOutputPath\(" scripts
printf '%s\n' "== path module usage =="
rg -n "from \"node:path\"|from 'node:path'|win32|posix|normalize|resolve" scripts/lib/operator-cli.mjs scripts/export-growth-doctor.mjs
printf '%s\n' "== tracked related tests/docs =="
rg -n -i "growth-doctor|resolveOutputPath|output=.*runs|output path" --glob '!node_modules' .

Repository: nish3451/tinystudio-in

Length of output: 16993


🏁 Script executed:

node - <<'JS'
import path from "node:path";

for (const [label, resolver] of [
  ["posix", path.posix.resolve],
  ["win32", path.win32.resolve]
]) {
  const resolved = resolver("service-root", "runs/growth-doctor.md");
  const current = resolved.split("/").slice(0, -1).join("/");
  const correct = path[label].dirname(resolved);
  console.log(JSON.stringify({ label, resolved, current, correct }));
}
JS

Repository: nish3451/tinystudio-in

Length of output: 501


Use dirname() for the resolved output path.

On Windows, resolvedOutputPath.split("/") produces an empty outputDir because Windows uses \ separators. The parent directory is not created, and writeFileSync() fails when it does not already exist.

Import dirname from node:path and call mkdirSync(dirname(resolvedOutputPath), { recursive: true }).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/export-growth-doctor.mjs` around lines 238 - 241, Update the
output-directory handling around resolvedOutputPath to import and use dirname
from node:path instead of splitting on "/". Pass dirname(resolvedOutputPath) to
mkdirSync with recursive creation before writeFileSync, preserving the existing
resolved output path.

Comment on lines +187 to +190
const resolvedOutputPath = resolveOutputPath(outputPath);
const outputDir = resolvedOutputPath.split("/").slice(0, -1).join("/");
if (outputDir) mkdirSync(outputDir, { recursive: true });
writeFileSync(outputPath, markdown);
writeFileSync(resolvedOutputPath, markdown);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- imports and path helpers ---'
sed -n '1,80p' scripts/export-growth-metrics.mjs
printf '%s\n' '--- target code ---'
sed -n '160,205p' scripts/export-growth-metrics.mjs
printf '%s\n' '--- path helper references ---'
rg -n -C 4 'resolveOutputPath|outputPath|writeFileSync|mkdirSync' scripts/export-growth-metrics.mjs
printf '%s\n' '--- native path behavior ---'
node - <<'JS'
const path = require('node:path');
const samples = [
  path.win32.resolve('metrics.md'),
  path.win32.resolve('reports', 'metrics.md'),
  path.posix.resolve('reports', 'metrics.md'),
];
for (const resolved of samples) {
  const splitDir = resolved.split('/').slice(0, -1).join('/');
  console.log(JSON.stringify({
    resolved,
    splitDir,
    dirname: path.dirname(resolved),
    win32Dirname: path.win32.dirname(resolved),
  }));
}
JS

Repository: nish3451/tinystudio-in

Length of output: 7104


🏁 Script executed:

#!/bin/bash
set -eu
file=$(fd -t f -i 'operator-cli\.mjs$' . | head -n 1)
if [ -z "$file" ]; then
  printf '%s\n' 'operator-cli.mjs not found'
  exit 0
fi
printf '--- %s ---\n' "$file"
rg -n -C 8 'resolveOutputPath|from "node:path"|path\.resolve|resolve\(' "$file"
printf '%s\n' '--- related path tests and callers ---'
rg -n -C 3 'resolveOutputPath' . -g '!node_modules' -g '!dist' -g '!build'

Repository: nish3451/tinystudio-in

Length of output: 251


🏁 Script executed:

#!/bin/bash
set -eu
file=$(fd -t f -i 'operator-cli\.mjs$' . | head -n 1)
if [ -z "$file" ]; then
  printf '%s\n' 'operator-cli.mjs not found'
  exit 0
fi
printf -- '--- %s ---\n' "$file"
rg -n -C 8 'resolveOutputPath|from "node:path"|path\.resolve|resolve\(' "$file"
printf '%s\n' '--- related path tests and callers ---'
rg -n -C 3 'resolveOutputPath' . -g '!node_modules' -g '!dist' -g '!build'

Repository: nish3451/tinystudio-in

Length of output: 30818


Use dirname() for the resolved output path.

On Windows, resolveOutputPath() returns backslash-separated paths. split("/") produces an empty outputDir, so mkdirSync() is skipped. writeFileSync() then fails when the parent directory does not exist.

Import dirname from node:path and use dirname(resolvedOutputPath).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/export-growth-metrics.mjs` around lines 187 - 190, Update the
output-directory calculation around resolvedOutputPath to import dirname from
node:path and call dirname(resolvedOutputPath) instead of splitting on "/";
retain the existing recursive mkdirSync and writeFileSync flow.

Comment on lines +59 to +61
const parityScratchPath = "runs/.internal-dashboard-parity.md";
const parity = runJson(["scripts/check-market-parity-readiness.mjs", "--skip-kit", `--output=${parityScratchPath}`]);
try { rmSync(join(serviceRoot, parityScratchPath), { force: true }); } catch {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove the parity scratch file when the parity command fails.

runJson(...) executes before the try statement. If it throws after creating the scratch file, rmSync(...) does not run. Wrap the command and cleanup in try/finally.

Proposed fix
 const parityScratchPath = "runs/.internal-dashboard-parity.md";
-const parity = runJson(["scripts/check-market-parity-readiness.mjs", "--skip-kit", `--output=${parityScratchPath}`]);
-try { rmSync(join(serviceRoot, parityScratchPath), { force: true }); } catch {}
+let parity;
+try {
+  parity = runJson(["scripts/check-market-parity-readiness.mjs", "--skip-kit", `--output=${parityScratchPath}`]);
+} finally {
+  rmSync(join(serviceRoot, parityScratchPath), { force: true });
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const parityScratchPath = "runs/.internal-dashboard-parity.md";
const parity = runJson(["scripts/check-market-parity-readiness.mjs", "--skip-kit", `--output=${parityScratchPath}`]);
try { rmSync(join(serviceRoot, parityScratchPath), { force: true }); } catch {}
const parityScratchPath = "runs/.internal-dashboard-parity.md";
let parity;
try {
parity = runJson(["scripts/check-market-parity-readiness.mjs", "--skip-kit", `--output=${parityScratchPath}`]);
} finally {
rmSync(join(serviceRoot, parityScratchPath), { force: true });
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/export-internal-dashboard.mjs` around lines 59 - 61, Wrap the parity
command and cleanup in a try/finally block so parityScratchPath is removed even
when runJson throws. Keep the existing runJson invocation and rmSync cleanup
behavior, with cleanup executing in the finally block.

Comment on lines +68 to +71
const resolvedOutputPath = resolveOutputPath(outputPath);
const outputDir = resolvedOutputPath.split("/").slice(0, -1).join("/");
if (outputDir) mkdirSync(outputDir, { recursive: true });
writeFileSync(outputPath, content);
writeFileSync(resolvedOutputPath, content);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- file context ---'
sed -n '1,110p' scripts/export-proof-library.mjs
printf '%s\n' '--- path helper references ---'
rg -n -C 4 'resolveOutputPath|split\("/"\)|dirname\(' scripts
printf '%s\n' '--- runtime path behavior ---'
node - <<'JS'
const { dirname, win32, posix } = require('node:path');
const samples = [
  '/tmp/out/proof.json',
  'C:\\work\\out\\proof.json',
  '\\\\server\\share\\out\\proof.json',
];
for (const value of samples) {
  console.log(JSON.stringify({
    value,
    splitSlashParent: value.split('/').slice(0, -1).join('/'),
    dirname: dirname(value),
    win32Dirname: win32.dirname(value),
    posixDirname: posix.dirname(value),
  }));
}
JS

Repository: nish3451/tinystudio-in

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- operator-cli path helper ---'
rg -n -C 12 'function resolveOutputPath|const resolveOutputPath|export .*resolveOutputPath' scripts/lib/operator-cli.mjs
printf '%s\n' '--- relevant imports and call sites ---'
sed -n '1,95p' scripts/lib/operator-cli.mjs
sed -n '1,10p' scripts/export-proof-library.mjs
sed -n '64,75p' scripts/export-proof-library.mjs
printf '%s\n' '--- isolated platform behavior ---'
node - <<'JS'
const { dirname, win32 } = require('node:path');
for (const value of [
  'C:\\work\\out\\proof.json',
  '\\\\server\\share\\out\\proof.json',
  'C:/work/out/proof.json',
]) {
  console.log(JSON.stringify({
    value,
    slashSplit: value.split('/').slice(0, -1).join('/'),
    hostDirname: dirname(value),
    windowsDirname: win32.dirname(value),
  }));
}
JS

Repository: nish3451/tinystudio-in

Length of output: 5188


Use dirname() for the resolved output path.

On Windows, resolveOutputPath() returns backslash separators. split("/") produces no parent directory, so mkdirSync() is skipped and writeFileSync() fails when the parent does not exist. Import dirname from node:path and use dirname(resolvedOutputPath).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/export-proof-library.mjs` around lines 68 - 71, Update the resolved
output directory logic near resolvedOutputPath to import and use dirname from
node:path instead of splitting on "/". Keep the existing conditional mkdirSync
behavior and writeFileSync flow unchanged.

Comment on lines +647 to +650
const resolvedOutputPath = resolveOutputPath(outputPath);
const outputDir = resolvedOutputPath.split("/").slice(0, -1).join("/");
if (outputDir) mkdirSync(outputDir, { recursive: true });
writeFileSync(outputPath, html);
writeFileSync(resolvedOutputPath, html);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- imports and target code ---'
sed -n '1,45p' scripts/export-prospect-outbox.mjs
sed -n '620,665p' scripts/export-prospect-outbox.mjs
printf '%s\n' '--- path-related usage ---'
rg -n -C 3 'resolveOutputPath|outputDir|writeFileSync|mkdirSync|dirname|outputPath' scripts/export-prospect-outbox.mjs
printf '%s\n' '--- repository platform assumptions ---'
rg -n -C 2 'export-prospect-outbox|Windows|win32|path separator|dirname' README.md package.json scripts test tests 2>/dev/null || true

Repository: nish3451/tinystudio-in

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -eu
sed -n '1,45p' scripts/export-prospect-outbox.mjs
sed -n '620,665p' scripts/export-prospect-outbox.mjs
rg -n -C 3 'resolveOutputPath|outputDir|writeFileSync|mkdirSync|dirname|outputPath' scripts/export-prospect-outbox.mjs

Repository: nish3451/tinystudio-in

Length of output: 4604


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- resolveOutputPath implementation ---'
sed -n '1,120p' scripts/lib/operator-cli.mjs
printf '%s\n' '--- all resolveOutputPath call sites ---'
rg -n -C 4 'resolveOutputPath' scripts --glob '*.mjs'
printf '%s\n' '--- cross-platform path behavior probe ---'
node - <<'JS'
const path = require("node:path");
for (const p of [
  "C:\\repo\\prospects\\outbox.html",
  "C:\\repo\\outbox.html",
  "prospects/outbox.html",
  "/repo/prospects/outbox.html",
  "outbox.html"
]) {
  console.log(JSON.stringify({
    input: p,
    posixSplitDir: p.split("/").slice(0, -1).join("/"),
    win32Dirname: path.win32.dirname(p),
    posixDirname: path.posix.dirname(p)
  }));
}
JS

Repository: nish3451/tinystudio-in

Length of output: 38115


🏁 Script executed:

#!/bin/bash
set -eu
sed -n '1,120p' scripts/lib/operator-cli.mjs
rg -n -C 4 'resolveOutputPath' scripts --glob '*.mjs'
node - <<'JS'
const path = require("node:path");
for (const p of [
  "C:\\repo\\prospects\\outbox.html",
  "C:\\repo\\outbox.html",
  "prospects/outbox.html",
  "/repo/prospects/outbox.html",
  "outbox.html"
]) {
  console.log(JSON.stringify({
    input: p,
    posixSplitDir: p.split("/").slice(0, -1).join("/"),
    win32Dirname: path.win32.dirname(p),
    posixDirname: path.posix.dirname(p)
  }));
}
JS

Repository: nish3451/tinystudio-in

Length of output: 37990


Use dirname() for the resolved output path.

On Windows, resolveOutputPath() returns backslash-separated paths. split("/") produces no directory component, so mkdirSync() is skipped and writeFileSync() fails when the parent directory is missing. Import dirname() from node:path and use it for outputDir.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/export-prospect-outbox.mjs` around lines 647 - 650, Update the
output-directory calculation near resolvedOutputPath to import and use dirname()
from node:path instead of splitting on "/"; retain the conditional
mkdirSync(outputDir, { recursive: true }) before writeFileSync so Windows paths
create their parent directory correctly.

Comment on lines +584 to +587
const resolvedOutputPath = resolveOutputPath(outputPath);
const outputDir = resolvedOutputPath.split("/").slice(0, -1).join("/");
if (outputDir) mkdirSync(outputDir, { recursive: true });
writeFileSync(outputPath, html);
writeFileSync(resolvedOutputPath, html);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use dirname() for the resolved output path.

When resolvedOutputPath uses Windows separators, split("/") returns no parent directory. mkdirSync() then does not create the required parent directory, and writeFileSync() can fail. Use dirname(resolvedOutputPath) from node:path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/export-recording-cockpit.mjs` around lines 584 - 587, Replace the
manual parent-directory extraction after resolveOutputPath with
dirname(resolvedOutputPath) from node:path, ensuring the import is available.
Keep the existing conditional mkdirSync behavior and writeFileSync flow
unchanged.

Comment on lines +121 to +132
const liveMetricsBeforeHelp = readFileSync(join(T, "growth-brain/ops/live-metrics.md"), "utf8")
for (const name of helpSurface) {
const helped = run([`scripts/${name}`, "--help"])
eq(helped.status, 0, `${name} --help must exit 0: ${helped.stderr || helped.stdout}`)
mat(helped.stdout, /Usage:/, `${name} --help must print usage`)
}
deq(readFileSync(join(T, "growth-brain/ops/live-metrics.md"), "utf8"), liveMetricsBeforeHelp, "--help must not overwrite live metrics")
deq(readFileSync(join(T, "growth-brain/ops/proof-library.md")), trackedArtifacts.get("growth-brain/ops/proof-library.md"), "--help must not overwrite the proof library")
eq(existsSync(join(T, "runs/daily-money-mission.md")), true, "daily mission must exist from regeneration")
const missionBeforeHelp = readFileSync(join(T, "runs/daily-money-mission.md"), "utf8")
run(["scripts/export-daily-money-mission.mjs", "--help"])
deq(readFileSync(join(T, "runs/daily-money-mission.md"), "utf8"), missionBeforeHelp, "--help must not overwrite the daily money mission")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test the -h help contract.

The loop tests only --help. A script can regress for -h without failing this active-surface test. Run the same status, usage, and artifact-preservation checks for both help flags.

Proposed fix
 for (const name of helpSurface) {
-  const helped = run([`scripts/${name}`, "--help"])
-  eq(helped.status, 0, `${name} --help must exit 0: ${helped.stderr || helped.stdout}`)
-  mat(helped.stdout, /Usage:/, `${name} --help must print usage`)
+  for (const flag of ["--help", "-h"]) {
+    const helped = run([`scripts/${name}`, flag])
+    eq(helped.status, 0, `${name} ${flag} must exit 0: ${helped.stderr || helped.stdout}`)
+    mat(helped.stdout, /Usage:/, `${name} ${flag} must print usage`)
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const liveMetricsBeforeHelp = readFileSync(join(T, "growth-brain/ops/live-metrics.md"), "utf8")
for (const name of helpSurface) {
const helped = run([`scripts/${name}`, "--help"])
eq(helped.status, 0, `${name} --help must exit 0: ${helped.stderr || helped.stdout}`)
mat(helped.stdout, /Usage:/, `${name} --help must print usage`)
}
deq(readFileSync(join(T, "growth-brain/ops/live-metrics.md"), "utf8"), liveMetricsBeforeHelp, "--help must not overwrite live metrics")
deq(readFileSync(join(T, "growth-brain/ops/proof-library.md")), trackedArtifacts.get("growth-brain/ops/proof-library.md"), "--help must not overwrite the proof library")
eq(existsSync(join(T, "runs/daily-money-mission.md")), true, "daily mission must exist from regeneration")
const missionBeforeHelp = readFileSync(join(T, "runs/daily-money-mission.md"), "utf8")
run(["scripts/export-daily-money-mission.mjs", "--help"])
deq(readFileSync(join(T, "runs/daily-money-mission.md"), "utf8"), missionBeforeHelp, "--help must not overwrite the daily money mission")
const liveMetricsBeforeHelp = readFileSync(join(T, "growth-brain/ops/live-metrics.md"), "utf8")
for (const name of helpSurface) {
for (const flag of ["--help", "-h"]) {
const helped = run([`scripts/${name}`, flag])
eq(helped.status, 0, `${name} ${flag} must exit 0: ${helped.stderr || helped.stdout}`)
mat(helped.stdout, /Usage:/, `${name} ${flag} must print usage`)
}
}
deq(readFileSync(join(T, "growth-brain/ops/live-metrics.md"), "utf8"), liveMetricsBeforeHelp, "--help must not overwrite live metrics")
deq(readFileSync(join(T, "growth-brain/ops/proof-library.md")), trackedArtifacts.get("growth-brain/ops/proof-library.md"), "--help must not overwrite the proof library")
eq(existsSync(join(T, "runs/daily-money-mission.md")), true, "daily mission must exist from regeneration")
const missionBeforeHelp = readFileSync(join(T, "runs/daily-money-mission.md"), "utf8")
run(["scripts/export-daily-money-mission.mjs", "--help"])
deq(readFileSync(join(T, "runs/daily-money-mission.md"), "utf8"), missionBeforeHelp, "--help must not overwrite the daily money mission")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/test-active-operator-surfaces.mjs` around lines 121 - 132, Extend the
help-surface loop in the active-operator test to run each script with both
--help and -h. Apply the same successful-exit and Usage: assertions to each
flag, and ensure the existing live-metrics and proof-library preservation checks
cover both invocations; likewise validate export-daily-money-mission.mjs with -h
while preserving the daily mission artifact.

Comment on lines +134 to +142
// Operator export scripts must refuse output paths that escape the service
// root, whether absolute, ".."-relative, or through a symlink.
const escapeProbes = [
["scripts/export-growth-metrics.mjs", ["--output=../escape-metrics.md"], join(T, "../escape-metrics.md")],
["scripts/export-growth-cockpit.mjs", ["--output=/tmp/escape-cockpit.html"], "/tmp/escape-cockpit.html"],
["scripts/export-daily-money-mission.mjs", ["--html=../../escape-mission.html"], join(T, "../../escape-mission.html")],
["scripts/export-market-proof-run.mjs", ["--output=runs/escape-run.md", "--loom-links=../escape-links.txt"], join(T, "../escape-links.txt")],
["scripts/export-market-benchmark.mjs", ["--ops=/tmp/escape-ops.md"], "/tmp/escape-ops.md"]
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

node --input-type=module <<'NODE'
import { isAbsolute, relative, resolve } from "node:path";

const root = resolve(process.cwd());
const raw = resolve(root, "runs/absolute-path-probe.md");
const resolved = resolve(root, raw);
const rel = relative(root, resolved);

if (!isAbsolute(raw) || rel.startsWith("..")) {
  throw new Error("Probe setup failed");
}
console.log({ raw, resolved, rel });
// `rel` is in-root, so a resolver that checks only `rel` accepts this absolute input.
NODE

Repository: nish3451/tinystudio-in

Length of output: 320


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- candidate files ---'
fd -i 'export|active-operator' . scripts test tests 2>/dev/null | head -80

printf '%s\n' '--- resolver definitions and uses ---'
rg -n -C 5 'resolveOutputPath|escapeProbes|isAbsolute|relative\\(' scripts . --glob '*.mjs' --glob '*.js' --glob '*.ts' --glob '*.json' 2>/dev/null | head -240

Repository: nish3451/tinystudio-in

Length of output: 3182


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- resolver references ---'
rg -n -C 8 'resolveOutputPath|escapeProbes' . --glob '*.mjs' --glob '*.js' --glob '*.ts' || true

printf '%s\n' '--- test section ---'
sed -n '110,165p' scripts/test-active-operator-surfaces.mjs

printf '%s\n' '--- resolver-like path validation ---'
rg -n -C 6 'relative\\(|resolve\\(|isAbsolute\\(|service.*root|output path|outputPath' scripts --glob '*.mjs' | head -320

Repository: nish3451/tinystudio-in

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- operator-cli resolver ---'
sed -n '1,75p' scripts/lib/operator-cli.mjs

printf '%s\n' '--- complete path-probe test ---'
sed -n '134,180p' scripts/test-active-operator-surfaces.mjs

printf '%s\n' '--- service root definition ---'
rg -n -C 4 'serviceRoot|SERVICE_REPO_ROOT' scripts/lib scripts --glob '*.mjs' | head -120

Repository: nish3451/tinystudio-in

Length of output: 14793


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- documented output-path policy ---'
rg -n -C 3 'absolute|output paths|stay inside|escape the service|escape.*repository' README.md docs scripts --glob '*.md' --glob '*.mjs' | head -240

printf '%s\n' '--- resolver-focused tests and package commands ---'
rg -n -C 5 'operator-cli|resolveOutputPath|active-operator-surfaces|escapeProbes' . --glob '!scripts/*.mjs' --glob '*.json' --glob '*.md' --glob '*.yml' --glob '*.yaml' || true

Repository: nish3451/tinystudio-in

Length of output: 21356


Reject absolute output paths inside the service repository.

resolve(root, raw) accepts an absolute raw path when it is inside root. Add an in-repository absolute-path probe and reject isAbsolute(raw) before calling resolve(root, raw).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/test-active-operator-surfaces.mjs` around lines 134 - 142, Update the
operator export path validation exercised by escapeProbes to reject any
isAbsolute(raw) output path before resolving it, including absolute paths that
fall within the service root. Add an in-repository absolute-path probe and
preserve the existing rejection coverage for parent-relative and symlink
escapes.

@nish3451

nish3451 commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator Author

Lane item verification: [unreviewed-by-grok] Growth/ops exporters ignore --help and overwrite tracked ops artifacts (live-metrics, proof-library) is fully addressed by this PR.

Repro against current origin/main (e0769ca): node scripts/export-growth-metrics.mjs --help and node scripts/export-proof-library.mjs --help both exit 0 while silently regenerating and overwriting growth-brain/ops/live-metrics.md and growth-brain/ops/proof-library.md (sentinel content destroyed).

Against this branch (a568523): both scripts print usage and exit 0 with the tracked artifacts untouched (sentinel preserved). npm run ci passes in full: exit 0, 0 failures across all suites (126/127/11/44/77/137/19 checks).

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f099aa029

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +348 to +349
write(resolveOutputPath(outputPath), markdown);
write(resolveOutputPath(opsPath, { flag: "--ops" }), markdown);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate every output path before writing artifacts

When a secondary path is unsafe—for example, market:benchmark -- --ops=/tmp/escape.md—line 348 overwrites the primary benchmark before line 349 rejects the escaping path and exits. The failed invocation therefore leaves a partially regenerated, potentially inconsistent output set; resolve all three paths before performing the first write.

Useful? React with 👍 / 👎.

Comment on lines +59 to +61
const parityScratchPath = "runs/.internal-dashboard-parity.md";
const parity = runJson(["scripts/check-market-parity-readiness.mjs", "--skip-kit", `--output=${parityScratchPath}`]);
try { rmSync(join(serviceRoot, parityScratchPath), { force: true }); } catch {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use a unique dashboard scratch file

When runs/.internal-dashboard-parity.md already exists, the parity checker first overwrites that operator-owned file and this cleanup then deletes it unconditionally. Since runs/ is a general ignored output directory, a dashboard export can destroy unrelated local data at this predictable name; create a unique temporary file for each invocation and remove only that file.

Useful? React with 👍 / 👎.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66bb043a13

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

`;

const outputDir = outputPath.split("/").slice(0, -1).join("/");
const resolvedOutputPath = resolveOutputPath(outputPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate the path before running dependency exporters

When growth:cockpit is invoked with an escaping path such as --output=/tmp/cockpit.html, validation does not occur until this line, after lines 32–43 have run eleven child exporters. The command exits nonzero but still overwrites in-repository cockpits, metrics, the proof library, mission, doctor report, and sender guide, so a rejected invocation is not atomic; resolve the requested path before launching any dependency exporters.

AGENTS.md reference: AGENTS.md:L2-L3

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

nish3451 added a commit that referenced this pull request Aug 11, 2026
The fleet keeps dispatching the same finding to multiple lanes, producing
duplicate same-fix PR pairs: #36/#44 and #55/#56 are byte-identical or
near-identical patches of the operator export --help fix, #39/#49 the
offername article fix, #40/#52 the recording exporter --help fix, plus
#38/#48/#51, #30/#61/#72, #45/#64, #42/#60, #43/#54 and #46/#74.

Add a PR Duplicate Guard:
- scripts/check-pr-duplicates.mjs compares a PR's diff against every other
  open PR (shared changed-file coverage >= 0.8 and patch similarity >= 0.5).
  Calibrated on all 79 open PRs on 2026-08-11: every pair above the
  thresholds was a genuine duplicate-cluster member, zero false positives.
- .github/workflows/pr-duplicate-guard.yml runs it on every PR event and
  posts one marker comment naming the duplicate(s) and the canonical PR; the
  check fails loudly when a duplicate is found. Informational, not required.
- scripts/test-pr-duplicates.mjs covers parsing, similarity, detection, and
  comment upsert with an injected API; wired into npm ci and npm test.
@nish3451

Copy link
Copy Markdown
Collaborator Author

Superseded by #56, which implements --help handling for all 29 operator/growth export scripts via the shared scripts/lib/operator-cli.mjs helper (this PR covers 20). Verified: main currently has the shared lib but zero exporters wired to it, and #56 is the only PR that wires all of them. Closing in favour of #56.

@nish3451 nish3451 closed this Aug 19, 2026
@nish3451

Copy link
Copy Markdown
Collaborator Author

Correction to my closing comment above: the keeper for this cluster is #160, not #56.

I had wrongly concluded that main had no exporters wired for --help. In fact main already wires 18 of 29 exporters to scripts/lib/operator-cli.mjs; only 11 remain, and #160 covers exactly those 11 using main's current helper. #56 has since been closed because it would revert main's newer operator-cli.mjs and re-apply an older calling convention across 18 already-finished exporters.

This PR stays closed either way — its content is superseded by what is already on main plus #160 — but the pointer should be to #160.

nish3451 added a commit that referenced this pull request Aug 21, 2026
…main by PR #80 (duplicate guard) (#234)

The duplicate-PR guard (PR #80, merged 2026-08-19) is now live on main,
preventing the same-fix duplication pattern at the CI level. The named
duplicate pairs (#36/#44, #39/#49, #40/#52) are functionally superseded:
their underlying fixes are on main via PRs #135, #145, and #178. The
prior 2026-08-15 lane run had concluded the guard existed but was stuck
unmerged; on 2026-08-19 it landed and the item is resolved at the
root-cause level.

Verification-only run (no product or test files touched):
- PR #80 source commit 2091c7a and merge commit 0a9909b are ancestors of origin/main
- scripts/test-pr-duplicates.mjs → 'test-pr-duplicates: ok' (exit 0)
- Superseding PRs #135 (d4f3ef4), #145 (fc44b42), #178 (77f6922) all on main
- Guard workflow runs on pull_request events; not a required status, so existing work never blocks

Co-authored-by: minimax-vps <minimax-vps@nish3451.dev>
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