feat(tooling): backport --sample to 6 more tools across 3 domains (#654)

Brings G9 JSON-output coverage from 19 to 25 tools (25/25 verified):

- business-growth: health_score_calculator (embedded 2-customer fixture),
  pipeline_analyzer (embedded 4-deal pipeline fixture)
- c-level-advisor: pmf_scorer (--sample flag for its existing sample_data(),
  suppressing the stdout notice that corrupted JSON piping),
  team_scaling_calculator (--sample flag for its embedded defaults)
- engineering-team: incident_triage (embedded synthetic ransomware event),
  contrast_checker (--sample as alias of the existing --demo)

Required-arg behavior unchanged when --sample is absent (still exits 2 with
a usage error). All gates green: G9 25/25, G8, dual-publish, path linter.

Together with the 5 #654-named tools, 4 JSON-honor fixes, and the pre-existing
sample-pattern tools, more than 20 tools now support the embedded-sample
convention — meeting #654's acceptance bar.

https://claude.ai/code/session_01CUWsrUNZP9jpxvAwq67UiT
This commit is contained in:
Claude 2026-06-11 15:56:18 +00:00
parent 61ca73ef13
commit 21f8d18a60
No known key found for this signature in database
6 changed files with 124 additions and 24 deletions

View file

@ -397,11 +397,38 @@ def format_json(results: List[Dict[str, Any]]) -> str:
# ---------------------------------------------------------------------------
# Embedded synthetic fixture for --sample (two customers across segments).
SAMPLE_DATA = {
"customers": [
{
"customer_id": "C-001",
"name": "Acme Corp",
"segment": "enterprise",
"arr": 240000,
"usage": {"dau_mau_ratio": 0.55, "license_utilization": 0.82},
"engagement": {"qbr_attendance": 1.0, "champion_engaged": True},
"support": {"open_tickets": 2, "csat": 4.6},
"relationship": {"nps": 9, "exec_sponsor": True},
},
{
"customer_id": "C-002",
"name": "Globex Ltd",
"segment": "smb",
"arr": 18000,
"usage": {"dau_mau_ratio": 0.12, "license_utilization": 0.35},
"engagement": {"qbr_attendance": 0.0, "champion_engaged": False},
"support": {"open_tickets": 7, "csat": 3.1},
"relationship": {"nps": 4, "exec_sponsor": False},
},
]
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Calculate multi-dimensional customer health scores with trend analysis."
)
parser.add_argument("input_file", help="Path to JSON file containing customer data")
parser.add_argument("input_file", nargs="?", help="Path to JSON file containing customer data")
parser.add_argument(
"--format",
choices=["text", "json"],
@ -409,17 +436,27 @@ def main() -> None:
dest="output_format",
help="Output format (default: text)",
)
parser.add_argument(
"--sample",
action="store_true",
help="Run with an embedded synthetic customer fixture (no input file needed)",
)
args = parser.parse_args()
try:
with open(args.input_file, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input_file}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in {args.input_file}: {e}", file=sys.stderr)
sys.exit(1)
if args.sample:
data = SAMPLE_DATA
else:
if not args.input_file:
parser.error("input_file is required (or use --sample)")
try:
with open(args.input_file, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input_file}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in {args.input_file}: {e}", file=sys.stderr)
sys.exit(1)
customers = data.get("customers", [])
if not customers:

View file

@ -448,6 +448,24 @@ def format_text_report(results: dict) -> str:
return "\n".join(lines)
# Embedded synthetic pipeline fixture for --sample.
SAMPLE_DATA = {
"quota": 1000000,
"average_cycle_days": 45,
"stages": ["discovery", "demo", "proposal", "negotiation", "closed_won"],
"deals": [
{"id": "D-1", "name": "Acme renewal", "stage": "proposal",
"value": 120000, "age_days": 30, "close_date": "2026-07-15"},
{"id": "D-2", "name": "Globex new logo", "stage": "discovery",
"value": 80000, "age_days": 10, "close_date": "2026-08-01"},
{"id": "D-3", "name": "Initech expansion", "stage": "negotiation",
"value": 200000, "age_days": 70, "close_date": "2026-06-30"},
{"id": "D-4", "name": "Umbrella upsell", "stage": "closed_won",
"value": 60000, "age_days": 50, "close_date": "2026-05-20"},
],
}
def main() -> None:
"""Main entry point for pipeline analyzer CLI."""
parser = argparse.ArgumentParser(
@ -455,7 +473,6 @@ def main() -> None:
)
parser.add_argument(
"--input",
required=True,
help="Path to JSON file containing pipeline data",
)
parser.add_argument(
@ -464,18 +481,28 @@ def main() -> None:
default="text",
help="Output format: json or text (default: text)",
)
parser.add_argument(
"--sample",
action="store_true",
help="Analyze an embedded synthetic pipeline (no input file needed)",
)
args = parser.parse_args()
try:
with open(args.input, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in {args.input}: {e}", file=sys.stderr)
sys.exit(1)
if args.sample:
data = SAMPLE_DATA
else:
if not args.input:
parser.error("--input is required (or use --sample)")
try:
with open(args.input, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in {args.input}: {e}", file=sys.stderr)
sys.exit(1)
# Validate required fields
required_fields = ["deals", "quota", "stages"]

View file

@ -556,6 +556,11 @@ def main():
action="store_true",
help="Output raw JSON instead of formatted report",
)
parser.add_argument(
"--sample",
action="store_true",
help="Run with the built-in sample data (no notice line — safe for JSON piping)",
)
args = parser.parse_args()
if args.input:
@ -568,6 +573,8 @@ def main():
except json.JSONDecodeError as e:
print(f"Error: invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
elif args.sample:
data = sample_data()
else:
print("No input file provided — running with sample data.\n")
data = sample_data()

View file

@ -528,9 +528,13 @@ if __name__ == "__main__":
"--json", action="store_true",
help="Output raw JSON instead of formatted report"
)
parser.add_argument(
"--sample", action="store_true",
help="Run with the embedded sample data (ignores input_file)"
)
args = parser.parse_args()
if args.input_file:
if args.input_file and not args.sample:
with open(args.input_file) as f:
data = json.load(f)
current_state = data["current_state"]

View file

@ -353,6 +353,11 @@ def build_parser() -> argparse.ArgumentParser:
action="store_true",
help="Show example output with sample color pairs",
)
parser.add_argument(
"--sample",
action="store_true",
help="Alias for --demo (repo-wide embedded-sample convention)",
)
return parser
@ -360,8 +365,8 @@ def main() -> int:
parser = build_parser()
args = parser.parse_args()
# --demo mode
if args.demo:
# --demo / --sample mode
if args.demo or args.sample:
run_demo(args.json_output)
return 0

View file

@ -584,6 +584,19 @@ def _print_text_report(result: dict) -> None:
# Main Entry Point
# ---------------------------------------------------------------------------
# Embedded synthetic security event for --sample (no file/stdin needed).
SAMPLE_EVENT = {
"event_type": "ransomware",
"source_ip": "203.0.113.50",
"destination_ip": "10.0.4.21",
"user_account": "svc-backup",
"hostname": "fileserver-02",
"process_name": "encryptor.exe",
"first_seen": "2026-06-10T01:30:00Z",
"detected_at": "2026-06-10T09:30:00Z",
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Incident Classification, Triage, and Escalation",
@ -627,12 +640,19 @@ Exit codes:
choices=["sev1", "sev2", "sev3", "sev4"],
help="Explicit severity override (skips taxonomy-derived severity)",
)
parser.add_argument(
"--sample",
action="store_true",
help="Triage an embedded synthetic ransomware event (no file/stdin needed)",
)
args = parser.parse_args()
# --- Load input ---
try:
if args.input:
if args.sample:
raw_event = SAMPLE_EVENT
elif args.input:
with open(args.input, "r", encoding="utf-8") as fh:
raw_event = json.load(fh)
else: