Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
517ee8a
fix(ops): make operator export scripts honor --help and refuse out-of…
nish3451 Aug 11, 2026
b1163a3
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 12, 2026
8dae7dd
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 12, 2026
212197f
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 12, 2026
6ca592d
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 12, 2026
e299770
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 12, 2026
86e2dc5
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 12, 2026
404e62c
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 12, 2026
b2164ef
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 12, 2026
36ab621
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 12, 2026
3506160
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 12, 2026
caff60f
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 13, 2026
6845789
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 13, 2026
aad3b20
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 13, 2026
fab7985
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 13, 2026
33aad2b
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 13, 2026
1955e41
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 13, 2026
b41a2de
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 13, 2026
4c9cf7e
Merge remote-tracking branch 'origin/main' into resolve/pr98-operator…
nish3451 Aug 18, 2026
1c95919
Merge branch 'main' into fix/operator-export-cli-help-lane1-20260812
nish3451 Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scripts/export-client-delivery-cockpit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { localIsoDate } from "./date-utils.mjs";
import { codeRoot, serviceRoot } from "./lib/runtime-roots.mjs";
import { resolveRepoPath } from "./lib/service-contract.mjs";
import { loadValidatedServiceClient } from "./lib/validated-service-client.mjs";
import { handleHelp } from "./lib/operator-cli.mjs";

const args = process.argv.slice(2);
handleHelp(args, `Usage: npm run client:cockpit -- clients/client-slug [--output=clients/client-slug/delivery-cockpit.html]`);
const clientPath = args[0];
const outputArg = args.find((arg) => arg.startsWith("--output="));
const repoRoot = serviceRoot;
Expand Down
9 changes: 6 additions & 3 deletions scripts/export-daily-money-mission.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import { sendChannelGuidance } from "./lib/send-channel-guidance.mjs";
import { routedContactPlan } from "./lib/contact-route.mjs";
import { canonicalProspectAsk } from "./lib/canonical-service-copy.mjs";
import { NO_GUARANTEE_CLIENT_SENTENCE } from "./lib/service-contract.mjs";
import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs";

handleHelp(process.argv.slice(2), `Usage: npm run growth:mission -- [--limit=5] [--output=runs/daily-money-mission.md] [--html=runs/daily-money-mission.html]`);
const limitArg = process.argv.find((arg) => arg.startsWith("--limit="));
const limit = limitArg ? Number(limitArg.split("=")[1]) : 5;
const outputArg = process.argv.find((arg) => arg.startsWith("--output="));
Expand Down Expand Up @@ -160,14 +162,15 @@ function missionFromMetrics(counts) {
}

function write(path, content) {
const dir = path.split("/").slice(0, -1).join("/");
const resolved = resolveOutputPath(path);

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 mission outputs before running exporters

When growth:mission receives an escaping --output or --html, validation does not reach this call until the final writes. Before then, the section at lines 201-222 runs ten child exporters and may refresh prospects/loom-links.txt; with an escaping --html, it also writes the requested Markdown output first. The command therefore reports a refusal only after overwriting repository artifacts, contrary to the plan's zero-write verification requirement. Resolve both destinations immediately after parsing them, before invoking exporters or updating the Loom template.

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

Useful? React with 👍 / 👎.

const dir = resolved.split("/").slice(0, -1).join("/");
if (dir) mkdirSync(dir, { recursive: true });
writeFileSync(path, `${String(content).replace(/[ \t]+$/gm, "").trimEnd()}\n`);
writeFileSync(resolved, `${String(content).replace(/[ \t]+$/gm, "").trimEnd()}\n`);
}

function ensureLoomLinksTemplate(prospects) {
const path = "prospects/loom-links.txt";
const existing = read(path);
const existing = read(resolveOutputPath(path));
const hasFilledLinks = existing
.split("\n")
.some((line) => {
Expand Down
7 changes: 5 additions & 2 deletions scripts/export-followup-cockpit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { listOutboundProspectFolders } from "./lib/outbound-prospects.mjs";
import { execFileSync } from "node:child_process";
import { localIsoDate } from "./date-utils.mjs";
import { sendChannelGuidance } from "./lib/send-channel-guidance.mjs";
import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs";

handleHelp(process.argv.slice(2), `Usage: npm run prospect:followups -- [--limit=10] [--output=prospects/followup-cockpit.html]`);
const limitArg = process.argv.find((arg) => arg.startsWith("--limit="));
const limit = limitArg ? Number(limitArg.split("=")[1]) : 10;
const outputArg = process.argv.find((arg) => arg.startsWith("--output="));
Expand Down Expand Up @@ -403,9 +405,10 @@ const html = `<!doctype html>
</html>
`;

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 follow-up output before refreshing messages

When a prospect has a due follow-up and --output escapes the repository, ensureMessage() at line 94 overwrites the prospect's next-message.md before this late validation exits. Validate the destination before enumerating due prospects so a refused follow-up export does not alter repository data.

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

Useful? React with 👍 / 👎.

const outputDir = resolvedOutputPath.split("/").slice(0, -1).join("/");
if (outputDir) mkdirSync(outputDir, { recursive: true });
writeFileSync(outputPath, html);
writeFileSync(resolvedOutputPath, html);

console.log(JSON.stringify({
status: "created",
Expand Down
7 changes: 5 additions & 2 deletions scripts/export-growth-cockpit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { execFileSync } from "node:child_process";
import { localIsoDate } from "./date-utils.mjs";
import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs";

handleHelp(process.argv.slice(2), `Usage: npm run growth:cockpit -- [--output=runs/growth-cockpit.html]`);
const outputArg = process.argv.find((arg) => arg.startsWith("--output="));
const outputPath = outputArg ? outputArg.split("=")[1] : "runs/growth-cockpit.html";
const today = localIsoDate();
Expand Down Expand Up @@ -241,9 +243,10 @@ const html = `<!doctype html>
</html>
`;

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 cockpit path before regenerating dependencies

When --output escapes the repository, this validation is reached only after the calls near the top of the script have run the recording, scoring, outbox, mission, metrics, proof, doctor, and sender exporters. Consequently, an invocation such as growth:cockpit -- --output=/tmp/x exits with a refusal but has already overwritten numerous default repository artifacts—the same accidental regeneration this safety work is meant to avoid. Resolve the requested output immediately after parsing arguments, before invoking any child exporter.

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

Useful? React with 👍 / 👎.

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`);

console.log(JSON.stringify({
status: "created",
Expand Down
7 changes: 5 additions & 2 deletions scripts/export-growth-doctor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { localIsoDate } from "./date-utils.mjs";
import { checkProspectReadiness, prospectWarningWeight } from "./lib/prospect-readiness.mjs";
import { listOutboundProspectFolders } from "./lib/outbound-prospects.mjs";
import { runRepoJson as runJson } from "./lib/runtime-roots.mjs";
import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs";

handleHelp(process.argv.slice(2), `Usage: npm run growth:doctor -- [--no-checks] [--plain] [--output=runs/growth-doctor.md]`);
const outputArg = process.argv.find((arg) => arg.startsWith("--output="));
const outputPath = outputArg ? outputArg.split("=")[1] : "runs/growth-doctor.md";
const skipChecks = process.argv.includes("--no-checks");
Expand Down Expand Up @@ -233,9 +235,10 @@ ${focusRows || "- No active focus items."}
Do not build more system surface until the current bottleneck is worked.
`;

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 doctor output before regenerating reports

When --output escapes the repository, validation occurs only here, after the metrics and rehearsal exporters have already overwritten their default artifacts at lines 151–154. Resolve the requested destination before invoking those exporters so a refused growth:doctor command remains side-effect free.

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

Useful? React with 👍 / 👎.

const outputDir = resolvedOutputPath.split("/").slice(0, -1).join("/");
if (outputDir) mkdirSync(outputDir, { recursive: true });
writeFileSync(outputPath, markdown);
writeFileSync(resolvedOutputPath, markdown);

const result = {
status,
Expand Down
7 changes: 5 additions & 2 deletions scripts/export-lead-scoring-cockpit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { listOutboundProspectFolders } from "./lib/outbound-prospects.mjs";
import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs";

handleHelp(process.argv.slice(2), `Usage: npm run prospect:score-cockpit -- [--limit=10] [--output=prospects/lead-scoring-cockpit.html]`);
const limitArg = process.argv.find((arg) => arg.startsWith("--limit="));
const limit = limitArg ? Number(limitArg.split("=")[1]) : 10;
const outputArg = process.argv.find((arg) => arg.startsWith("--output="));
Expand Down Expand Up @@ -295,9 +297,10 @@ const html = `<!doctype html>
</html>
`;

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 Anchor prospect reads to the service root

When SERVICE_REPO_ROOT differs from the current working directory, this now writes the cockpit under the service repository, but the prospect scan at line 45 still reads the relative prospects directory from the current working directory. Consequently, running the exporter from another workspace can copy that workspace's prospect names and details into the service repository's HTML; resolve the input root against serviceRoot as well before generating the output.

Useful? React with 👍 / 👎.

const outputDir = resolvedOutputPath.split("/").slice(0, -1).join("/");
if (outputDir) mkdirSync(outputDir, { recursive: true });
writeFileSync(outputPath, html);
writeFileSync(resolvedOutputPath, html);

console.log(JSON.stringify({
status: "created",
Expand Down
4 changes: 3 additions & 1 deletion scripts/export-managed-it-one-pager.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env node
import { readFileSync, writeFileSync } from "node:fs";
import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs";

handleHelp(process.argv.slice(2), `Usage: npm run sales:one-pager --`);
const inputPath = "growth-brain/sales/managed-it-one-page-offer.md";
const outputPath = "growth-brain/sales/managed-it-one-page-offer.html";

Expand Down Expand Up @@ -148,7 +150,7 @@ ${body}
</html>
`;

writeFileSync(outputPath, html);
writeFileSync(resolveOutputPath(outputPath), html);

console.log(JSON.stringify({
status: "created",
Expand Down
11 changes: 7 additions & 4 deletions scripts/export-market-learning-review.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { join } from "node:path";
import { localIsoDate } from "./date-utils.mjs";
import { listOutboundProspectFolders } from "./lib/outbound-prospects.mjs";
import { runRepoJson as runJson } from "./lib/runtime-roots.mjs";
import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs";

handleHelp(process.argv.slice(2), `Usage: npm run market:learn -- [--limit=10] [--output=runs/market-learning-review.md] [--html=runs/market-learning-review.html]`);
const outputArg = process.argv.find((arg) => arg.startsWith("--output="));
const htmlArg = process.argv.find((arg) => arg.startsWith("--html="));
const limitArg = process.argv.find((arg) => arg.startsWith("--limit="));
Expand All @@ -25,10 +27,11 @@ function json(path) {
return existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {};
}

function write(path, content) {
const dir = path.split("/").slice(0, -1).join("/");
function write(path, content, flag = "--output") {
const resolved = resolveOutputPath(path, { flag });
const dir = resolved.split("/").slice(0, -1).join("/");
if (dir) mkdirSync(dir, { recursive: true });
writeFileSync(path, content);
writeFileSync(resolved, content);
}

function lineValue(content, pattern, fallback = "") {
Expand Down Expand Up @@ -296,7 +299,7 @@ const html = `<!doctype html>
`;

write(outputPath, markdown);
write(htmlPath, html);
write(htmlPath, html, "--html");
Comment on lines 301 to +302

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 learning-review HTML before regenerating metrics

With an escaping --html, this second write rejects the path only after the metrics exporter at line 62 has refreshed growth-brain/ops/live-metrics.md and line 301 has overwritten the Markdown learning review. Resolve both output paths before running exporters or writing either artifact so an invalid destination cannot cause partial regeneration.

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

Useful? React with 👍 / 👎.


console.log(JSON.stringify({
status: review.status,
Expand Down
11 changes: 7 additions & 4 deletions scripts/export-market-proof-cockpit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { sendChannelGuidance } from "./lib/send-channel-guidance.mjs";
import { routedContactPlan } from "./lib/contact-route.mjs";
import { classifyOutboundProspect } from "./lib/outbound-prospects.mjs";
import { runRepoJson as runJson } from "./lib/runtime-roots.mjs";
import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs";

const args = process.argv.slice(2);
handleHelp(args, `Usage: npm run market:proof-cockpit -- [prospects/loom-links.txt] [--limit=5] [--output=runs/market-proof-cockpit.md] [--html=runs/market-proof-cockpit.html]`);
const inputPath = args.find((arg) => !arg.startsWith("--")) || "prospects/loom-links.txt";
const outputArg = args.find((arg) => arg.startsWith("--output="));
const htmlArg = args.find((arg) => arg.startsWith("--html="));
Expand All @@ -27,10 +29,11 @@ function json(path) {
return existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {};
}

function write(path, content) {
const dir = path.split("/").slice(0, -1).join("/");
function write(path, content, flag = "--output") {
const resolved = resolveOutputPath(path, { flag });
const dir = resolved.split("/").slice(0, -1).join("/");
if (dir) mkdirSync(dir, { recursive: true });
writeFileSync(path, content);
writeFileSync(resolved, content);
}

function clean(value, fallback = "") {
Expand Down Expand Up @@ -415,7 +418,7 @@ const html = `<!doctype html>
`;

write(outputPath, markdown);
write(htmlPath, html);
write(htmlPath, html, "--html");
Comment on lines 420 to +421

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 proof-cockpit HTML before writing artifacts

When --html escapes the repository, the command reaches this validation only after check-market-proof-run.mjs has overwritten runs/market-proof-run-check.md and line 420 has overwritten the requested Markdown cockpit. The command then reports refusal despite already modifying repository artifacts, so resolve both destinations before invoking the checker or performing either write.

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

Useful? React with 👍 / 👎.


const result = {
status: cockpitStatus,
Expand Down
7 changes: 5 additions & 2 deletions scripts/export-prospect-outbox.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { sendChannelGuidance } from "./lib/send-channel-guidance.mjs";
import { isValidLoomUrl } from "./lib/loom-url.mjs";
import { routedContactPlan, routeToChannel } from "./lib/contact-route.mjs";
import { listOutboundProspectFolders } from "./lib/outbound-prospects.mjs";
import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs";

handleHelp(process.argv.slice(2), `Usage: npm run prospect:outbox -- [--limit=20] [--output=prospects/outbox.html]`);
const limitArg = process.argv.find((arg) => arg.startsWith("--limit="));
const limit = limitArg ? Number(limitArg.split("=")[1]) : 20;
const outputArg = process.argv.find((arg) => arg.startsWith("--output="));
Expand Down Expand Up @@ -642,9 +644,10 @@ const html = `<!doctype html>
</html>
`;

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 outbox output before regenerating messages

When an approved send-package prospect exists and --output escapes the repository, the outbox calls ensureMessage() at line 121 and overwrites next-message.md before reaching this validation. Resolve the destination before building the prospect list so an invalid outbox request cannot modify prospect artifacts.

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

Useful? React with 👍 / 👎.

const outputDir = resolvedOutputPath.split("/").slice(0, -1).join("/");
if (outputDir) mkdirSync(outputDir, { recursive: true });
writeFileSync(outputPath, html);
writeFileSync(resolvedOutputPath, html);

console.log(JSON.stringify({
status: "created",
Expand Down
7 changes: 5 additions & 2 deletions scripts/export-sales-cockpit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { join } from "node:path";
import { localIsoDate } from "./date-utils.mjs";
import { agencyConfig } from "./lib/agency-config.mjs";
import { listOutboundProspectFolders } from "./lib/outbound-prospects.mjs";
import { handleHelp, resolveOutputPath } from "./lib/operator-cli.mjs";

handleHelp(process.argv.slice(2), `Usage: npm run prospect:sales-cockpit -- [--limit=20] [--output=prospects/sales-cockpit.html]`);
const limitArg = process.argv.find((arg) => arg.startsWith("--limit="));
const limit = limitArg ? Number(limitArg.split("=")[1]) : 20;
const outputArg = process.argv.find((arg) => arg.startsWith("--output="));
Expand Down Expand Up @@ -432,9 +434,10 @@ const html = `<!doctype html>
</html>
`;

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

console.log(JSON.stringify({
status: "created",
Expand Down
Loading