diff --git a/.lane/reports/fix-recording-exporters-honor-help.md b/.lane/reports/fix-recording-exporters-honor-help.md new file mode 100644 index 00000000..11a6feb3 --- /dev/null +++ b/.lane/reports/fix-recording-exporters-honor-help.md @@ -0,0 +1,79 @@ +# Lane 1 — Recording exporters honor `--help` + +## Item + +- [unreviewed-by-opus] Recording exporters (teleprompter / rehearsal / cockpit / queue) ignore --help and write arti + +## Branch + +`fix/recording-exporters-honor-help` (off `origin/main` @ `497d690`) + +## Root cause + +The four recording-batch operator scripts in `scripts/` never tested +`process.argv` for `--help` / `-h` and never routed their `--output=` / +`--html=` flags through the shared operator CLI safety helpers. Calling any +of them with `--help` ran the full batch export and silently rewrote the +recording-batch artifact in `prospects/` — the same hazard the prior +`#135` and `#99` fixes closed for the growth/ops exporters and the +retired broad-service writers. + +| Script | Default artifact | +|---|---| +| `scripts/export-recording-cockpit.mjs` | `prospects/recording-cockpit.html` | +| `scripts/export-recording-queue.mjs` | `prospects/recording-queue.md` | +| `scripts/export-recording-rehearsal-check.mjs` | `prospects/recording-rehearsal-check.{md,html}` | +| `scripts/export-recording-teleprompter.mjs` | `prospects/recording-teleprompter.html` | + +## Fix + +All four scripts now follow the established `handleHelp` / +`resolveOutputPath` pattern that the prior recording-batch refactor +peers use: + +1. Import `handleHelp` and `resolveOutputPath` from + `./lib/operator-cli.mjs`. +2. Call `handleHelp(args, "Usage: ...")` immediately after parsing + `process.argv`, before any work. +3. Resolve every operator-supplied `--output=` / `--html=` value through + `resolveOutputPath(..., { fallback })` so paths that escape the + service repository are refused, mirroring the safety contract used + by `export-market-proof-run.mjs`, `export-internal-dashboard.mjs`, + etc. + +## Tests + +- Extended + `scripts/test-active-operator-surfaces.mjs`'s `remainingHelpSurface` + list with the four recording-batch scripts. The shared loop already + enforces: + - exit 0 for both `--help` and `-h`, + - `Usage:` printed on stdout, + - no rewrite of any tracked, runtime, or retired artifact. +- Ran `node scripts/test-active-operator-surfaces.mjs` end-to-end — + passes (`Active operator surface checks passed.`). +- Manual probes (run in `/tmp/test-recording-help`, removed after): + - `node scripts/export-recording-queue.mjs --help` → exits 0, prints + `Usage: node scripts/export-recording-queue.mjs [--limit=5] [--output=prospects/recording-queue.md]`, + no `prospects/` created. + - `node scripts/export-recording-cockpit.mjs -h` → exits 0, prints + usage, no artifact. + - `node scripts/export-recording-teleprompter.mjs --help` → exits 0, + prints usage, no artifact. + - `node scripts/export-recording-rehearsal-check.mjs --help` → exits + 0, prints usage, no artifact. + - `--output=/tmp/escape-*.md` → refused with + `Refusing --output=...: the path escapes the repository`, exit 1, + no file written outside the service root for all four scripts. + +## Files + +- `scripts/export-recording-cockpit.mjs` — add `handleHelp` + + `resolveOutputPath`. +- `scripts/export-recording-queue.mjs` — same. +- `scripts/export-recording-rehearsal-check.mjs` — same; routes both + `--output` and `--html` through `resolveOutputPath`. +- `scripts/export-recording-teleprompter.mjs` — same. +- `scripts/test-active-operator-surfaces.mjs` — register the four + scripts in `remainingHelpSurface` so the shared `--help` / `-h` + contract stays enforced. diff --git a/scripts/export-recording-cockpit.mjs b/scripts/export-recording-cockpit.mjs index 99f258d1..4250e770 100644 --- a/scripts/export-recording-cockpit.mjs +++ b/scripts/export-recording-cockpit.mjs @@ -5,12 +5,16 @@ import { execFileSync } from "node:child_process"; import { checkProspectReadiness, prospectWarningWeight } from "./lib/prospect-readiness.mjs"; import { sendChannelGuidance } from "./lib/send-channel-guidance.mjs"; import { routedContactPlan } from "./lib/contact-route.mjs"; +import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs"; import { listOutboundProspectFolders } from "./lib/outbound-prospects.mjs"; -const limitArg = process.argv.find((arg) => arg.startsWith("--limit=")); +const args = process.argv.slice(2); +handleHelp(args, `Usage: node scripts/export-recording-cockpit.mjs [--limit=5] [--output=prospects/recording-cockpit.html]`); +const limitArg = args.find((arg) => arg.startsWith("--limit=")); const limit = limitArg ? Number(limitArg.split("=")[1]) : 5; -const outputArg = process.argv.find((arg) => arg.startsWith("--output=")); -const outputPath = outputArg ? outputArg.split("=")[1] : "prospects/recording-cockpit.html"; +const outputArg = args.find((arg) => arg.startsWith("--output=")); +const outputRel = outputArg ? outputArg.split("=")[1] : "prospects/recording-cockpit.html"; +const outputPath = resolveOutputPath(outputRel, { fallback: "prospects/recording-cockpit.html" }); function listFolders(root) { return listOutboundProspectFolders(root); diff --git a/scripts/export-recording-queue.mjs b/scripts/export-recording-queue.mjs index bc61af3b..2a6dbcbd 100644 --- a/scripts/export-recording-queue.mjs +++ b/scripts/export-recording-queue.mjs @@ -1,13 +1,17 @@ #!/usr/bin/env node import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; +import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs"; import { listOutboundProspectFolders } from "./lib/outbound-prospects.mjs"; import { checkProspectReadiness, prospectWarningWeight } from "./lib/prospect-readiness.mjs"; -const limitArg = process.argv.find((arg) => arg.startsWith("--limit=")); +const args = process.argv.slice(2); +handleHelp(args, `Usage: node scripts/export-recording-queue.mjs [--limit=5] [--output=prospects/recording-queue.md]`); +const limitArg = args.find((arg) => arg.startsWith("--limit=")); const limit = limitArg ? Number(limitArg.split("=")[1]) : 5; -const outputArg = process.argv.find((arg) => arg.startsWith("--output=")); -const outputPath = outputArg ? outputArg.split("=")[1] : "prospects/recording-queue.md"; +const outputArg = args.find((arg) => arg.startsWith("--output=")); +const outputRel = outputArg ? outputArg.split("=")[1] : "prospects/recording-queue.md"; +const outputPath = resolveOutputPath(outputRel, { fallback: "prospects/recording-queue.md" }); function listFolders(root) { return listOutboundProspectFolders(root); diff --git a/scripts/export-recording-rehearsal-check.mjs b/scripts/export-recording-rehearsal-check.mjs index b0c99220..d629a1eb 100644 --- a/scripts/export-recording-rehearsal-check.mjs +++ b/scripts/export-recording-rehearsal-check.mjs @@ -5,16 +5,19 @@ import { checkProspectReadiness, prospectWarningWeight } from "./lib/prospect-re import { sendChannelGuidance } from "./lib/send-channel-guidance.mjs"; import { routedContactPlan } from "./lib/contact-route.mjs"; import { localIsoDate } from "./date-utils.mjs"; +import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs"; import { listOutboundProspectFolders } from "./lib/outbound-prospects.mjs"; const args = process.argv.slice(2); +handleHelp(args, `Usage: node scripts/export-recording-rehearsal-check.mjs [--limit=5] [--output=prospects/recording-rehearsal-check.md] [--html=prospects/recording-rehearsal-check.html] [--include-smoke]`); const limitArg = args.find((arg) => arg.startsWith("--limit=")); const outputArg = args.find((arg) => arg.startsWith("--output=")); const htmlArg = args.find((arg) => arg.startsWith("--html=")); const includeSmoke = args.includes("--include-smoke"); const limit = limitArg ? Number(limitArg.split("=")[1]) : 5; -const outputPath = outputArg ? outputArg.split("=")[1] : "prospects/recording-rehearsal-check.md"; -const htmlPath = htmlArg ? htmlArg.split("=")[1] : "prospects/recording-rehearsal-check.html"; +const outputRel = outputArg ? outputArg.split("=")[1] : "prospects/recording-rehearsal-check.md"; +const outputPath = resolveOutputPath(outputRel, { fallback: "prospects/recording-rehearsal-check.md" }); +const htmlPath = resolveOutputPath(htmlArg ? htmlArg.split("=")[1] : "prospects/recording-rehearsal-check.html", { flag: "--html", fallback: "prospects/recording-rehearsal-check.html" }); const today = localIsoDate(); function read(path) { diff --git a/scripts/export-recording-teleprompter.mjs b/scripts/export-recording-teleprompter.mjs index 22a76726..b5c21bef 100644 --- a/scripts/export-recording-teleprompter.mjs +++ b/scripts/export-recording-teleprompter.mjs @@ -4,12 +4,16 @@ import { dirname, join, relative } from "node:path"; import { checkProspectReadiness, prospectWarningWeight } from "./lib/prospect-readiness.mjs"; import { sendChannelGuidance } from "./lib/send-channel-guidance.mjs"; import { routedContactPlan } from "./lib/contact-route.mjs"; +import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs"; import { listOutboundProspectFolders } from "./lib/outbound-prospects.mjs"; -const limitArg = process.argv.find((arg) => arg.startsWith("--limit=")); +const args = process.argv.slice(2); +handleHelp(args, `Usage: node scripts/export-recording-teleprompter.mjs [--limit=5] [--output=prospects/recording-teleprompter.html]`); +const limitArg = args.find((arg) => arg.startsWith("--limit=")); const limit = limitArg ? Number(limitArg.split("=")[1]) : 5; -const outputArg = process.argv.find((arg) => arg.startsWith("--output=")); -const outputPath = outputArg ? outputArg.split("=")[1] : "prospects/recording-teleprompter.html"; +const outputArg = args.find((arg) => arg.startsWith("--output=")); +const outputRel = outputArg ? outputArg.split("=")[1] : "prospects/recording-teleprompter.html"; +const outputPath = resolveOutputPath(outputRel, { fallback: "prospects/recording-teleprompter.html" }); function listFolders(root) { return listOutboundProspectFolders(root); diff --git a/scripts/test-active-operator-surfaces.mjs b/scripts/test-active-operator-surfaces.mjs index f63078b5..19976bd5 100644 --- a/scripts/test-active-operator-surfaces.mjs +++ b/scripts/test-active-operator-surfaces.mjs @@ -129,7 +129,11 @@ try { "export-owned-handoff-loom-cockpit.mjs", "export-owned-product-case-studies.mjs", "export-owned-product-workflow-proofs.mjs", - "export-owned-startup-proof-capture.mjs" + "export-owned-startup-proof-capture.mjs", + "export-recording-cockpit.mjs", + "export-recording-queue.mjs", + "export-recording-rehearsal-check.mjs", + "export-recording-teleprompter.mjs" ] // The growth/ops exporters that overwrite tracked ACTIVE_OPERATOR_ARTIFACTS // must also honor --help/-h: the same exit 0 + usage contract, and they