fullstackgtm 0.49.0 → 0.50.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/DATA-FLOWS.md +1 -0
  3. package/README.md +48 -0
  4. package/dist/acquireCheckpoint.d.ts +33 -0
  5. package/dist/acquireCheckpoint.js +149 -0
  6. package/dist/acquireLinkedIn.d.ts +2 -0
  7. package/dist/acquireLinkedIn.js +1 -1
  8. package/dist/cli/audit.js +6 -1
  9. package/dist/cli/auth.js +25 -0
  10. package/dist/cli/backfill.js +4 -0
  11. package/dist/cli/call.js +28 -6
  12. package/dist/cli/draft.js +11 -1
  13. package/dist/cli/enrich.js +286 -81
  14. package/dist/cli/fix.js +6 -3
  15. package/dist/cli/help.js +30 -26
  16. package/dist/cli/icp.js +15 -1
  17. package/dist/cli/market.js +14 -1
  18. package/dist/cli/planOutput.d.ts +5 -0
  19. package/dist/cli/planOutput.js +27 -0
  20. package/dist/cli/plans.js +269 -24
  21. package/dist/cli/schedule.js +22 -11
  22. package/dist/cli/shared.d.ts +3 -1
  23. package/dist/cli/shared.js +2 -2
  24. package/dist/cli/signals.js +22 -3
  25. package/dist/cli/tam.js +10 -1
  26. package/dist/cli/ui.d.ts +10 -5
  27. package/dist/cli/ui.js +28 -9
  28. package/dist/connectors/hubspot.d.ts +4 -0
  29. package/dist/connectors/hubspot.js +36 -18
  30. package/dist/connectors/linkedin.d.ts +2 -0
  31. package/dist/connectors/linkedin.js +5 -0
  32. package/dist/connectors/prospectSources.d.ts +23 -0
  33. package/dist/connectors/prospectSources.js +21 -3
  34. package/dist/enrich.d.ts +44 -1
  35. package/dist/hostedAcquireCheckpoint.d.ts +43 -0
  36. package/dist/hostedAcquireCheckpoint.js +129 -0
  37. package/dist/hostedPatchPlan.d.ts +87 -0
  38. package/dist/hostedPatchPlan.js +275 -0
  39. package/dist/icp.js +13 -2
  40. package/dist/index.d.ts +4 -1
  41. package/dist/index.js +3 -0
  42. package/dist/planStore.d.ts +14 -0
  43. package/dist/planStore.js +73 -0
  44. package/dist/progress.d.ts +2 -2
  45. package/dist/progress.js +2 -2
  46. package/docs/api.md +24 -0
  47. package/docs/architecture.md +9 -0
  48. package/package.json +1 -1
  49. package/skills/fullstackgtm/SKILL.md +1 -1
  50. package/src/acquireCheckpoint.ts +186 -0
  51. package/src/acquireLinkedIn.ts +3 -1
  52. package/src/cli/audit.ts +5 -1
  53. package/src/cli/auth.ts +24 -0
  54. package/src/cli/backfill.ts +3 -0
  55. package/src/cli/call.ts +25 -6
  56. package/src/cli/draft.ts +9 -1
  57. package/src/cli/enrich.ts +325 -80
  58. package/src/cli/fix.ts +5 -3
  59. package/src/cli/help.ts +31 -26
  60. package/src/cli/icp.ts +13 -1
  61. package/src/cli/market.ts +12 -1
  62. package/src/cli/planOutput.ts +30 -0
  63. package/src/cli/plans.ts +259 -25
  64. package/src/cli/schedule.ts +21 -15
  65. package/src/cli/shared.ts +2 -1
  66. package/src/cli/signals.ts +22 -3
  67. package/src/cli/tam.ts +8 -1
  68. package/src/cli/ui.ts +31 -13
  69. package/src/connectors/hubspot.ts +41 -17
  70. package/src/connectors/linkedin.ts +6 -0
  71. package/src/connectors/prospectSources.ts +46 -4
  72. package/src/enrich.ts +47 -1
  73. package/src/hostedAcquireCheckpoint.ts +156 -0
  74. package/src/hostedPatchPlan.ts +291 -0
  75. package/src/icp.ts +14 -2
  76. package/src/index.ts +20 -0
  77. package/src/planStore.ts +87 -0
  78. package/src/progress.ts +2 -2
package/dist/cli/help.js CHANGED
@@ -176,11 +176,11 @@ Usage:
176
176
  fullstackgtm suggest --plan-id <id> | --plan <path> [source options] [--json] [--out <path>]
177
177
  derive values for requires_human_* placeholders
178
178
  from snapshot evidence, with confidence + reasons
179
- fullstackgtm plans list [--status <s>] | show <id> | reject <id>
179
+ fullstackgtm plans list [--status <s>] | show <id> [--verbose] | sync | reject <id>
180
180
  fullstackgtm plans approve <id> --operations <ids|all> [--value <opId>=<v>]
181
181
  fullstackgtm plans approve <id> --values-from <suggestions.json> [--min-confidence high|low] [--include-creates]
182
182
  fullstackgtm plans recover <id> --acknowledge-uncertain-writes
183
- fullstackgtm apply --plan-id <id> --provider <name>
183
+ fullstackgtm apply --plan-id <id> --provider <name> [--verbose|--json]
184
184
  fullstackgtm apply --plan-id <id> --channel outbox (render approved openers; transmits nothing)
185
185
  fullstackgtm apply --plan <path> --provider <name> --approve <ids|all> [options]
186
186
  fullstackgtm audit-log export [--out <path>] | verify --in <path> tamper-evident apply-run record
@@ -200,7 +200,11 @@ Profiles (multi-organization use):
200
200
  Plan lifecycle:
201
201
  audit --save persists the dry-run plan to ~/.fullstackgtm/plans. Approve
202
202
  specific operations (optionally with --value <opId>=<v> for placeholders),
203
- then apply by id — the store enforces approval and records every run.
203
+ then apply by id — the store enforces approval and records every run. When
204
+ paired, local and hosted are replicas of the same immutable plan: either
205
+ capable surface may approve or apply, and each CLI check-in imports missing
206
+ approvals and exact operation receipts. Use \`plans sync\` for an explicit
207
+ check-in; offline changes reconcile later without blocking local work.
204
208
 
205
209
  Authentication (checked in order):
206
210
  1. --token-env <name> explicit env var for this invocation (hubspot)
@@ -495,26 +499,26 @@ export const HELP = {
495
499
  seeAlso: ["audit", "plans", "apply"],
496
500
  },
497
501
  plans: {
498
- summary: "plan lifecycle: list / show / approve / reject saved plans",
502
+ summary: "replicated plan lifecycle: list / show / sync / approve / reject",
499
503
  phase: "Govern",
500
504
  synopsis: [
501
- "fullstackgtm plans list [--status <s>] | show <id> | reject <id>",
505
+ "fullstackgtm plans list [--status <s>] | show <id> [--verbose] | sync | reject <id>",
502
506
  "fullstackgtm plans approve <id> --operations <ids|all> [--value <opId>=<v>]",
503
507
  "fullstackgtm plans approve <id> --values-from <suggestions.json> [--min-confidence high|low]",
504
508
  "fullstackgtm plans recover <id> --acknowledge-uncertain-writes",
505
509
  ],
506
- detail: "Approval is explicit and per-operation; placeholders need a concrete --value or a suggestion to be approvable.",
510
+ detail: "Approval is explicit and per-operation; placeholders need a concrete --value or a suggestion to be approvable. When paired, hosted and CLI are eventually consistent replicas of one immutable plan. `plans sync` explicitly exchanges approval revisions, execution state, and exact per-operation receipts; ordinary plan commands also check in best-effort. Either surface may apply when its CRM connector supports the selected operations.",
507
511
  seeAlso: ["audit", "suggest", "apply"],
508
512
  },
509
513
  apply: {
510
514
  summary: "write ONLY explicitly approved operations to a provider",
511
515
  phase: "Govern / Verify",
512
516
  synopsis: [
513
- "fullstackgtm apply --plan-id <id> --provider <name>",
517
+ "fullstackgtm apply --plan-id <id> --provider <name> [--verbose|--json]",
514
518
  "fullstackgtm apply --plan-id <id> --channel outbox (render approved drafted openers to the outbox; transmits nothing)",
515
519
  "fullstackgtm apply --plan <path> --provider <name> --approve <ids|all> [--value <opId>=<v>]",
516
520
  ],
517
- detail: "The only verb that mutates a CRM. Writes only operations approved via `plans approve` or `--approve`, with compare-and-set and readback. Never writes requires_human_* placeholders without a --value override.",
521
+ detail: "The CLI CRM-write verb. Writes only operations approved locally or imported from the shared hosted replica, with an online execution claim when available, stable operation ids, compare-and-set, resolve-before-create, and readback. Default output is a compact outcome card; --verbose prints the full run document and --json preserves structured output. Hosted may execute the same synchronized plan when it has a compatible CRM connection; its exact operation receipts are imported on the next CLI check-in. Never writes requires_human_* placeholders without a --value override.",
518
522
  seeAlso: ["plans", "suggest", "audit-log"],
519
523
  },
520
524
  "audit-log": {
@@ -593,7 +597,7 @@ export const BESPOKE_HELP = ["init", "call", "market", "tam", "enrich", "schedul
593
597
  export const GLOBAL_FLAGS = ["--help", "--full"];
594
598
  export const GLOBAL_SHORT_FLAGS = ["-h"];
595
599
  export const SOURCE_FLAGS = ["--provider", "--token-env", "--input", "--demo", "--sample", "--seed", "--today"];
596
- export const AUDIT_FLAGS = ["--config", "--allow-plugins", "--no-plugins", "--rules", "--stale-days", "--fail-on", "--save", "--dry-run", "--json", "--out", "--full"];
600
+ export const AUDIT_FLAGS = ["--config", "--allow-plugins", "--no-plugins", "--rules", "--stale-days", "--fail-on", "--save", "--dry-run", "--json", "--out", "--full", "--verbose"];
597
601
  // Complete per-command flag registry used by runCli's fail-closed flag
598
602
  // validation. Keep this next to HELP so focused help, machine capabilities,
599
603
  // and the parser safety gate have one command inventory to reconcile against.
@@ -601,7 +605,7 @@ export const COMMAND_FLAGS = {
601
605
  init: ["--source", "--provider", "--out", "--force"],
602
606
  login: ["--via", "--hosted", "--private-token", "--token", "--key", "--api-key", "--client-id", "--client-secret", "--scopes", "--port", "--oauth", "--device", "--instance-url", "--login-url", "--no-validate"],
603
607
  logout: [],
604
- doctor: ["--json"],
608
+ doctor: ["--verbose", "--json"],
605
609
  capabilities: ["--json"],
606
610
  "robot-docs": [],
607
611
  profiles: ["--json"],
@@ -615,27 +619,27 @@ export const COMMAND_FLAGS = {
615
619
  hierarchy: [...SOURCE_FLAGS, "--json", "--out"],
616
620
  relationships: [...SOURCE_FLAGS, "--account-id", "--domain", "--json", "--out"],
617
621
  route: [...SOURCE_FLAGS, "--match", "--no-inherit-owner", "--reassign-owned", "--policy", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
618
- fix: [...SOURCE_FLAGS, "--config", "--allow-plugins", "--no-plugins", "--rule", "--provider", "--min-confidence", "--include-creates", "--yes", "--confirm", "--dry-run"],
619
- "bulk-update": [...SOURCE_FLAGS, "--where", "--set", "--guard", "--require", "--create-task", "--archive", "--force-archive-duplicates", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
620
- dedupe: [...SOURCE_FLAGS, "--key", "--keep", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
621
- reassign: [...SOURCE_FLAGS, "--from", "--assign-unowned", "--to", "--objects", "--where", "--except-deal-stage", "--include-closed-deals", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
622
- backfill: [...SOURCE_FLAGS, "--since", "--pipeline", "--match-property", "--skip-unmatched", "--save", "--dry-run", "--json"],
623
- enrich: [...SOURCE_FLAGS, "--config", "--source", "--icp", "--input", "--provider", "--stale-days", "--assign-owner", "--objects", "--max", "--staged-run", "--run-label", "--label", "--runs", "--save", "--dry-run", "--json", "--out"],
624
- call: [...SOURCE_FLAGS, "--transcript", "--title", "--source", "--model", "--deterministic", "--heuristics", "--llm", "--json", "--ndjson", "--out", "--call", "--call-type", "--rubric", "--list", "--list-rubrics", "--attendees", "--domain", "--deal", "--save", "--dry-run"],
622
+ fix: [...SOURCE_FLAGS, "--config", "--allow-plugins", "--no-plugins", "--rule", "--provider", "--min-confidence", "--include-creates", "--yes", "--confirm", "--dry-run", "--verbose"],
623
+ "bulk-update": [...SOURCE_FLAGS, "--where", "--set", "--guard", "--require", "--create-task", "--archive", "--force-archive-duplicates", "--reason", "--max-operations", "--save", "--dry-run", "--verbose", "--json", "--out"],
624
+ dedupe: [...SOURCE_FLAGS, "--key", "--keep", "--reason", "--max-operations", "--save", "--dry-run", "--verbose", "--json", "--out"],
625
+ reassign: [...SOURCE_FLAGS, "--from", "--assign-unowned", "--to", "--objects", "--where", "--except-deal-stage", "--include-closed-deals", "--reason", "--max-operations", "--save", "--dry-run", "--verbose", "--json", "--out"],
626
+ backfill: [...SOURCE_FLAGS, "--since", "--pipeline", "--match-property", "--skip-unmatched", "--save", "--dry-run", "--verbose", "--json"],
627
+ enrich: [...SOURCE_FLAGS, "--config", "--source", "--icp", "--input", "--provider", "--list", "--stale-days", "--assign-owner", "--objects", "--max", "--scan-limit", "--staged-run", "--run-label", "--label", "--runs", "--save", "--dry-run", "--verbose", "--json", "--out"],
628
+ call: [...SOURCE_FLAGS, "--transcript", "--title", "--source", "--model", "--deterministic", "--heuristics", "--llm", "--verbose", "--json", "--ndjson", "--out", "--call", "--call-type", "--rubric", "--list", "--list-rubrics", "--attendees", "--domain", "--deal", "--save", "--dry-run"],
625
629
  suggest: [...SOURCE_FLAGS, "--plan-id", "--plan", "--json", "--out"],
626
- plans: ["--status", "--operations", "--value", "--values-from", "--min-confidence", "--include-creates", "--acknowledge-uncertain-writes", "--json"],
627
- apply: ["--plan", "--plan-id", "--provider", "--channel", "--token-env", "--approve", "--value", "--json", "--config"],
630
+ plans: ["--status", "--operations", "--value", "--values-from", "--min-confidence", "--include-creates", "--acknowledge-uncertain-writes", "--verbose", "--json"],
631
+ apply: ["--plan", "--plan-id", "--provider", "--channel", "--token-env", "--approve", "--value", "--verbose", "--json", "--config"],
628
632
  "audit-log": ["--in", "--out", "--json"],
629
633
  merge: ["--input", "--out", "--json"],
630
- market: ["--category", "--config", "--vendor", "--snapshot", "--task-account", "--task-deal", "--capture-run", "--run", "--prior-run", "--diff", "--from", "--calls", "--anchor", "--min-mentions", "--max-claims", "--promote-lift", "--model", "--format", "--auto", "--unverified", "--save", "--dry-run", "--json", "--out"],
631
- tam: [...SOURCE_FLAGS, "--source", "--name", "--icp", "--accounts", "--acv", "--acv-basis", "--acv-from-crm", "--deal-period", "--buyers-per-account", "--cross-checks", "--max", "--max-credits", "--usd-per-credit", "--dry-run", "--confirm", "--cron", "--label", "--save", "--json", "--out"],
632
- icp: [...SOURCE_FLAGS, "--name", "--signals-from", "--with-history", "--prompt", "--min-score", "--from-judge", "--golden", "--against-outcomes", "--min-accuracy", "--model", "--label", "--save", "--json", "--out"],
633
- signals: ["--bucket", "--source", "--connector", "--connector-opt", "--watchlist", "--keywords", "--from", "--label", "--since", "--account", "--contact", "--unjudged", "--touch", "--result", "--save", "--json", "--explain"],
634
- draft: ["--from-judge", "--min-score", "--prompt", "--model", "--channel", "--save", "--dry-run", "--json", "--out"],
635
- schedule: ["--cron", "--label", "--provider", "--trigger", "--runs", "--json"],
634
+ market: ["--category", "--config", "--vendor", "--snapshot", "--task-account", "--task-deal", "--capture-run", "--run", "--prior-run", "--diff", "--from", "--calls", "--anchor", "--min-mentions", "--max-claims", "--promote-lift", "--model", "--format", "--auto", "--unverified", "--save", "--dry-run", "--verbose", "--json", "--out"],
635
+ tam: [...SOURCE_FLAGS, "--source", "--name", "--icp", "--accounts", "--acv", "--acv-basis", "--acv-from-crm", "--deal-period", "--buyers-per-account", "--cross-checks", "--max", "--max-credits", "--usd-per-credit", "--dry-run", "--confirm", "--cron", "--label", "--save", "--verbose", "--json", "--out"],
636
+ icp: [...SOURCE_FLAGS, "--name", "--signals-from", "--with-history", "--prompt", "--min-score", "--from-judge", "--golden", "--against-outcomes", "--min-accuracy", "--model", "--label", "--save", "--verbose", "--json", "--out"],
637
+ signals: ["--bucket", "--source", "--connector", "--connector-opt", "--watchlist", "--keywords", "--from", "--label", "--since", "--account", "--contact", "--unjudged", "--touch", "--result", "--save", "--verbose", "--json", "--explain"],
638
+ draft: ["--from-judge", "--min-score", "--prompt", "--model", "--channel", "--save", "--dry-run", "--verbose", "--json", "--out"],
639
+ schedule: ["--cron", "--label", "--provider", "--trigger", "--timer", "--runs", "--verbose", "--json"],
636
640
  };
637
641
  export const FLAGS_WITH_VALUES = new Set([
638
- "--account", "--account-id", "--accounts", "--acv", "--acv-basis", "--after", "--anchor", "--api-key", "--approve", "--archive", "--assign-owner", "--attendees", "--before", "--bucket", "--buyers-per-account", "--call", "--call-type", "--calls", "--capture-run", "--category", "--channel", "--client", "--client-id", "--client-secret", "--config", "--connector", "--connector-opt", "--contact", "--cron", "--cross-checks", "--deal", "--deal-period", "--diff", "--domain", "--email", "--except-deal-stage", "--format", "--from", "--from-judge", "--golden", "--guard", "--icp", "--in", "--input", "--instance-url", "--keep", "--key", "--keywords", "--label", "--login-url", "--match", "--match-property", "--max", "--max-claims", "--max-credits", "--max-examples", "--max-operations", "--min-accuracy", "--min-confidence", "--min-mentions", "--min-score", "--model", "--name", "--objects", "--operations", "--out", "--pipeline", "--plan", "--plan-id", "--policy", "--port", "--prepared-by", "--prior-run", "--profile", "--promote-lift", "--prompt", "--provider", "--reason", "--require", "--result", "--rubric", "--rule", "--rules", "--run", "--run-label", "--runs", "--scopes", "--seed", "--set", "--signals-from", "--since", "--snapshot", "--source", "--staged-run", "--stale-days", "--status", "--task-account", "--task-deal", "--title", "--to", "--today", "--token", "--token-env", "--touch", "--transcript", "--trigger", "--usd-per-credit", "--value", "--values-from", "--vendor", "--via", "--watchlist", "--where",
642
+ "--account", "--account-id", "--accounts", "--acv", "--acv-basis", "--after", "--anchor", "--api-key", "--approve", "--archive", "--assign-owner", "--attendees", "--before", "--bucket", "--buyers-per-account", "--call", "--call-type", "--calls", "--capture-run", "--category", "--channel", "--client", "--client-id", "--client-secret", "--config", "--connector", "--connector-opt", "--contact", "--cron", "--cross-checks", "--deal", "--deal-period", "--diff", "--domain", "--email", "--except-deal-stage", "--format", "--from", "--from-judge", "--golden", "--guard", "--icp", "--in", "--input", "--instance-url", "--keep", "--key", "--keywords", "--label", "--list", "--login-url", "--match", "--match-property", "--max", "--max-claims", "--max-credits", "--max-examples", "--max-operations", "--min-accuracy", "--min-confidence", "--min-mentions", "--min-score", "--model", "--name", "--objects", "--operations", "--out", "--pipeline", "--plan", "--plan-id", "--policy", "--port", "--prepared-by", "--prior-run", "--profile", "--promote-lift", "--prompt", "--provider", "--reason", "--require", "--result", "--rubric", "--rule", "--rules", "--run", "--run-label", "--runs", "--scan-limit", "--scopes", "--seed", "--set", "--signals-from", "--since", "--snapshot", "--source", "--staged-run", "--stale-days", "--status", "--task-account", "--task-deal", "--timer", "--title", "--to", "--today", "--token", "--token-env", "--touch", "--transcript", "--trigger", "--usd-per-credit", "--value", "--values-from", "--vendor", "--via", "--watchlist", "--where",
639
643
  ]);
640
644
  // Lifecycle-grouped front door. One line per verb, organized by the
641
645
  // Prevent→Detect→Remediate→Verify loop so a new user sees ~6 jobs, not 22 verbs.
package/dist/cli/icp.js CHANGED
@@ -9,6 +9,20 @@ import { DEFAULT_GOLDEN_NOW_ISO, DEFAULT_GOLDEN_SET, DEFAULT_MIN_ACCURACY, defau
9
9
  import { loadIcp, numericOption, option, readSnapshot, resolveLlmBaseUrls, saveRequested } from "./shared.js";
10
10
  import { createStatusLine } from "./ui.js";
11
11
  import { unknownSubcommandError } from "./suggest.js";
12
+ function renderJudgeDecisions(decisions) {
13
+ if (decisions.length === 0)
14
+ return "No accounts cleared the score threshold.";
15
+ const lines = [`ICP decisions (${decisions.length})`, ""];
16
+ for (const decision of decisions) {
17
+ const target = decision.contact?.email || decision.contact?.title;
18
+ lines.push(`${decision.decision.toUpperCase().padEnd(7)} ${String(decision.score).padStart(3)} ${decision.accountDomain}${target ? ` · ${target}` : ""}`);
19
+ if (decision.whyNow)
20
+ lines.push(` ${decision.whyNow.replace(/\s+/g, " ").trim()}`);
21
+ if (decision.play)
22
+ lines.push(` Next: ${decision.play.replace(/\s+/g, " ").trim()}`);
23
+ }
24
+ return lines.join("\n");
25
+ }
12
26
  /**
13
27
  * `icp` — develop and inspect the Ideal Customer Profile that targets acquire.
14
28
  * The CLI can't run AskUserQuestion itself; `icp interview` emits the question
@@ -133,7 +147,7 @@ ops. Develop one by interview, then \`enrich acquire\` picks up ./icp.json.
133
147
  }
134
148
  decisions = decisions.filter((d) => d.score >= minScore);
135
149
  // 5) Ranked decisions to stdout (JSON), guidance to stderr.
136
- console.log(JSON.stringify(decisions, null, 2));
150
+ console.log(rest.includes("--json") || rest.includes("--verbose") ? JSON.stringify(decisions, null, 2) : renderJudgeDecisions(decisions));
137
151
  console.error(`Judged ${unjudged.length} signal(s) across ${new Set(decisions.map((d) => d.accountDomain)).size} account(s): ` +
138
152
  `${decisions.filter((d) => d.decision === "send").length} send, ` +
139
153
  `${decisions.filter((d) => d.decision === "nurture").length} nurture, ` +
@@ -481,9 +481,22 @@ Rules:
481
481
  writeFileSync(resolve(process.cwd(), outPath), output);
482
482
  console.log(`Wrote ${outPath}`);
483
483
  }
484
- else {
484
+ else if (rest.includes("--verbose") || option(rest, "--format")) {
485
485
  console.log(output);
486
486
  }
487
+ else {
488
+ const fronts = computeFrontStates(config, set);
489
+ const counts = new Map();
490
+ for (const front of fronts)
491
+ counts.set(front.state, (counts.get(front.state) ?? 0) + 1);
492
+ console.log(`${config.category} market · ${set.runLabel}`);
493
+ console.log(`${fronts.length} claim fronts · ${["open", "contested", "owned", "saturated", "vacant"].filter((state) => counts.has(state)).map((state) => `${counts.get(state)} ${state}`).join(" · ")}`);
494
+ for (const front of fronts) {
495
+ const owner = front.loudVendorIds.length ? ` · ${front.loudVendorIds.join(", ")}` : "";
496
+ console.log(`\n${front.state.toUpperCase()} ${front.claimId}${owner}`);
497
+ }
498
+ console.log("\nUse --verbose for the full evidence report, or --out <path> to write it.");
499
+ }
487
500
  return;
488
501
  }
489
502
  if (subcommand === "axes") {
@@ -0,0 +1,5 @@
1
+ import type { PatchPlan } from "../types.ts";
2
+ export declare function compactPlan(plan: PatchPlan, options?: {
3
+ saved?: boolean;
4
+ }): string;
5
+ export declare function verbosePlanRequested(args: readonly string[]): boolean;
@@ -0,0 +1,27 @@
1
+ import { box, colorEnabled, paint, truncateToWidth } from "./ui.js";
2
+ export function compactPlan(plan, options = {}) {
3
+ const counts = new Map();
4
+ for (const operation of plan.operations) {
5
+ const action = operation.operation || operation.field || "change";
6
+ counts.set(action, (counts.get(action) ?? 0) + 1);
7
+ }
8
+ const mix = [...counts]
9
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
10
+ .slice(0, 4)
11
+ .map(([action, count]) => `${count} ${action}`)
12
+ .join(" · ");
13
+ const lines = [
14
+ truncateToWidth(plan.title, 100),
15
+ `Status ${options.saved ? "SAVED · NEEDS APPROVAL" : "DRY RUN · NOTHING WRITTEN"}`,
16
+ truncateToWidth(`Scope ${plan.operations.length} operation${plan.operations.length === 1 ? "" : "s"}${mix ? ` · ${mix}` : ""}`, 100),
17
+ truncateToWidth(`Effect ${plan.summary}`, 100),
18
+ options.saved ? `Next fullstackgtm plans show ${plan.id}` : "Next re-run with --save to stage this plan for approval",
19
+ ];
20
+ return [
21
+ ...box(lines, paint(colorEnabled(process.stdout)), "Plan preview"),
22
+ `Details: re-run with --verbose${options.saved ? `; JSON: fullstackgtm plans show ${plan.id} --json` : "; machine output: --json"}`,
23
+ ].join("\n");
24
+ }
25
+ export function verbosePlanRequested(args) {
26
+ return args.includes("--verbose") || args.includes("--full");
27
+ }