|
| 1 | +import fs from 'fs'; |
| 2 | +import path from 'path'; |
| 3 | +import { |
| 4 | + AGENT_SKILL_STEPS, |
| 5 | + createSkillWorkflow, |
| 6 | +} from '../agent-skill/index.js'; |
| 7 | +import type { Workflow, WorkflowConfig } from '../workflow-step.js'; |
| 8 | +import type { WorkflowRun } from '../../agent/agent-runner.js'; |
| 9 | +import type { WizardSession } from '../../wizard-session.js'; |
| 10 | +import { AUDIT_ABORT_CASES } from '../audit/detect.js'; |
| 11 | +import { |
| 12 | + AUDIT_CHECKS_FILE, |
| 13 | + AUDIT_CHECKS_KEY, |
| 14 | + type AuditCheck, |
| 15 | +} from '../audit/types.js'; |
| 16 | +import { AUDIT_SEED_CHECKS } from '../audit/seed.js'; |
| 17 | +import { logToFile } from '../../../utils/debug'; |
| 18 | + |
| 19 | +const AUDIT3000_REPORT_FILE = 'posthog-audit-3000-report.md'; |
| 20 | + |
| 21 | +// Extra checks the v3000 audit adds on top of the base 10. IDs must match |
| 22 | +// those referenced in the audit-3000 skill's step files (Event Quality, |
| 23 | +// stale feature-flag review, session replay [fix + optimize], per-product |
| 24 | +// use-case expansion, and phase markers for the post-flags chain). |
| 25 | +const AUDIT3000_EXTRA_CHECKS: AuditCheck[] = [ |
| 26 | + // ── Event Quality (Step 5) ── |
| 27 | + { |
| 28 | + id: 'event-naming-standardization', |
| 29 | + area: 'Event Quality', |
| 30 | + label: 'Event naming convention is consistent', |
| 31 | + status: 'pending', |
| 32 | + }, |
| 33 | + { |
| 34 | + id: 'event-duplicates-and-bloat', |
| 35 | + area: 'Event Quality', |
| 36 | + label: 'No duplicate or bloated event capture', |
| 37 | + status: 'pending', |
| 38 | + }, |
| 39 | + { |
| 40 | + id: 'event-quality-context-review', |
| 41 | + area: 'Event Quality', |
| 42 | + label: 'Event property context reviewed', |
| 43 | + status: 'pending', |
| 44 | + }, |
| 45 | + { |
| 46 | + id: 'event-usage-coverage', |
| 47 | + area: 'Event Quality', |
| 48 | + label: 'Captured events match insights / dashboards usage', |
| 49 | + status: 'pending', |
| 50 | + }, |
| 51 | + // ── Feature Flags (Step 6) ── |
| 52 | + { |
| 53 | + id: 'stale-feature-flags-reviewed', |
| 54 | + area: 'Feature Flags', |
| 55 | + label: 'Stale feature flags reviewed', |
| 56 | + status: 'pending', |
| 57 | + }, |
| 58 | + // ── Session Replay — fix (Step 6b) ── |
| 59 | + { |
| 60 | + id: 'replay-minimum-duration-set', |
| 61 | + area: 'Session Replay', |
| 62 | + label: 'Minimum duration set on init', |
| 63 | + status: 'pending', |
| 64 | + }, |
| 65 | + { |
| 66 | + id: 'replay-mask-config', |
| 67 | + area: 'Session Replay', |
| 68 | + label: 'Mask config covers sensitive surfaces', |
| 69 | + status: 'pending', |
| 70 | + }, |
| 71 | + { |
| 72 | + id: 'replay-disabled-in-test-envs', |
| 73 | + area: 'Session Replay', |
| 74 | + label: 'Disabled in test / CI environments', |
| 75 | + status: 'pending', |
| 76 | + }, |
| 77 | + { |
| 78 | + id: 'replay-strict-minimum-duration', |
| 79 | + area: 'Session Replay', |
| 80 | + label: 'Strict minimum duration enforced', |
| 81 | + status: 'pending', |
| 82 | + }, |
| 83 | + // ── Session Replay — optimize (Step 6b cost wave) ── |
| 84 | + { |
| 85 | + id: 'replay-sampling-rate', |
| 86 | + area: 'Session Replay — Optimize', |
| 87 | + label: 'Sampling rate tuned for cost', |
| 88 | + status: 'pending', |
| 89 | + }, |
| 90 | + { |
| 91 | + id: 'replay-triggers-configured', |
| 92 | + area: 'Session Replay — Optimize', |
| 93 | + label: 'Triggers configured (event / URL / flag)', |
| 94 | + status: 'pending', |
| 95 | + }, |
| 96 | + { |
| 97 | + id: 'replay-network-recording-filtered', |
| 98 | + area: 'Session Replay — Optimize', |
| 99 | + label: 'Network recording filtered', |
| 100 | + status: 'pending', |
| 101 | + }, |
| 102 | + { |
| 103 | + id: 'replay-mobile-sampling', |
| 104 | + area: 'Session Replay — Optimize', |
| 105 | + label: 'Mobile sampling configured', |
| 106 | + status: 'pending', |
| 107 | + }, |
| 108 | + // ── Use Case: Expansion (Step 9) ── |
| 109 | + { |
| 110 | + id: 'expansion-product-analytics', |
| 111 | + area: 'Use Case: Expansion', |
| 112 | + label: 'Product analytics coverage', |
| 113 | + status: 'pending', |
| 114 | + }, |
| 115 | + { |
| 116 | + id: 'expansion-error-tracking', |
| 117 | + area: 'Use Case: Expansion', |
| 118 | + label: 'Error tracking coverage', |
| 119 | + status: 'pending', |
| 120 | + }, |
| 121 | + { |
| 122 | + id: 'expansion-llm-observability', |
| 123 | + area: 'Use Case: Expansion', |
| 124 | + label: 'LLM observability coverage', |
| 125 | + status: 'pending', |
| 126 | + }, |
| 127 | + { |
| 128 | + id: 'expansion-session-replay', |
| 129 | + area: 'Use Case: Expansion', |
| 130 | + label: 'Session replay coverage', |
| 131 | + status: 'pending', |
| 132 | + }, |
| 133 | + { |
| 134 | + id: 'expansion-feature-flags', |
| 135 | + area: 'Use Case: Expansion', |
| 136 | + label: 'Feature flags coverage', |
| 137 | + status: 'pending', |
| 138 | + }, |
| 139 | + { |
| 140 | + id: 'expansion-surveys', |
| 141 | + area: 'Use Case: Expansion', |
| 142 | + label: 'Surveys coverage', |
| 143 | + status: 'pending', |
| 144 | + }, |
| 145 | + { |
| 146 | + id: 'expansion-logs', |
| 147 | + area: 'Use Case: Expansion', |
| 148 | + label: 'Logs coverage', |
| 149 | + status: 'pending', |
| 150 | + }, |
| 151 | + { |
| 152 | + id: 'expansion-web-analytics', |
| 153 | + area: 'Use Case: Expansion', |
| 154 | + label: 'Web analytics coverage', |
| 155 | + status: 'pending', |
| 156 | + }, |
| 157 | + // ── Additional Sections (Steps 7, 8, 10 phase markers) ── |
| 158 | + // Tracked in the ledger so the UI can surface "did it run / was it |
| 159 | + // skipped" alongside the regular checks. use-case-expansion is omitted |
| 160 | + // because the eight `expansion-*` checks above cover that phase. |
| 161 | + { |
| 162 | + id: 'customer-enrichment', |
| 163 | + area: 'Additional Sections', |
| 164 | + label: 'Customer enrichment (Harmonic + PDL)', |
| 165 | + status: 'pending', |
| 166 | + }, |
| 167 | + { |
| 168 | + id: 'use-case-match', |
| 169 | + area: 'Additional Sections', |
| 170 | + label: 'Use-case match', |
| 171 | + status: 'pending', |
| 172 | + }, |
| 173 | + { |
| 174 | + id: 'final-report', |
| 175 | + area: 'Additional Sections', |
| 176 | + label: 'Final audit report written', |
| 177 | + status: 'pending', |
| 178 | + }, |
| 179 | +]; |
| 180 | + |
| 181 | +const AUDIT3000_SEED_CHECKS: AuditCheck[] = [ |
| 182 | + ...AUDIT_SEED_CHECKS, |
| 183 | + ...AUDIT3000_EXTRA_CHECKS, |
| 184 | +]; |
| 185 | + |
| 186 | +// Audit-3000 has its own arcade-flavoured intro / run / outro screens. The |
| 187 | +// shared audit screens stay reserved for the original `audit` workflow. |
| 188 | +const AUDIT3000_SCREEN_BY_STEP: Record<string, string> = { |
| 189 | + intro: 'audit-3000-intro', |
| 190 | + run: 'audit-3000-run', |
| 191 | + outro: 'audit-3000-outro', |
| 192 | +}; |
| 193 | + |
| 194 | +const seedAudit3000Ledger = (installDir: string): void => { |
| 195 | + const target = path.join(installDir, AUDIT_CHECKS_FILE); |
| 196 | + const tmp = `${target}.tmp`; |
| 197 | + fs.writeFileSync(tmp, JSON.stringify(AUDIT3000_SEED_CHECKS, null, 2), 'utf8'); |
| 198 | + fs.renameSync(tmp, target); |
| 199 | + logToFile( |
| 200 | + `seedAudit3000Ledger: wrote ${AUDIT3000_SEED_CHECKS.length} entries to ${target}`, |
| 201 | + ); |
| 202 | +}; |
| 203 | + |
| 204 | +const seedBeforeAudit3000Run = (session: WizardSession): void => { |
| 205 | + seedAudit3000Ledger(session.installDir); |
| 206 | + session.frameworkContext[AUDIT_CHECKS_KEY] = AUDIT3000_SEED_CHECKS; |
| 207 | +}; |
| 208 | + |
| 209 | +const withAudit3000Screens = (steps: Workflow): Workflow => |
| 210 | + steps.map((step) => { |
| 211 | + const override = AUDIT3000_SCREEN_BY_STEP[step.id]; |
| 212 | + return override ? { ...step, screen: override } : step; |
| 213 | + }); |
| 214 | + |
| 215 | +const audit3000Steps: Workflow = withAudit3000Screens(AGENT_SKILL_STEPS); |
| 216 | + |
| 217 | +const baseConfig = createSkillWorkflow({ |
| 218 | + skillId: 'audit-3000', |
| 219 | + command: 'audit-3000', |
| 220 | + flowKey: 'audit-3000', |
| 221 | + description: |
| 222 | + 'Audit an existing PostHog integration (v3000 — adds event quality, stale-flag hygiene, customer enrichment, use-case match)', |
| 223 | + integrationLabel: 'audit-3000', |
| 224 | + customPrompt: |
| 225 | + 'Run the audit-3000 skill end-to-end. Follow the step chain starting at references/1-version.md. Do not modify any project files — only create the final audit report and (when enrichment is enabled) the enrichment report.', |
| 226 | + successMessage: `Audit complete! View the report at ./${AUDIT3000_REPORT_FILE}`, |
| 227 | + reportFile: AUDIT3000_REPORT_FILE, |
| 228 | + docsUrl: 'https://posthog.com/docs/product-analytics/best-practices', |
| 229 | + spinnerMessage: 'Running PostHog Audit 3000...', |
| 230 | + estimatedDurationMinutes: 6, |
| 231 | + requires: ['posthog-integration'], |
| 232 | + abortCases: AUDIT_ABORT_CASES, |
| 233 | +}); |
| 234 | + |
| 235 | +const audit3000Run = async (session: WizardSession): Promise<WorkflowRun> => { |
| 236 | + seedBeforeAudit3000Run(session); |
| 237 | + |
| 238 | + if (!baseConfig.run) { |
| 239 | + throw new Error('audit-3000 workflow has no run configuration.'); |
| 240 | + } |
| 241 | + |
| 242 | + return typeof baseConfig.run === 'function' |
| 243 | + ? baseConfig.run(session) |
| 244 | + : baseConfig.run; |
| 245 | +}; |
| 246 | + |
| 247 | +export const audit3000Config: WorkflowConfig = { |
| 248 | + ...baseConfig, |
| 249 | + steps: audit3000Steps, |
| 250 | + run: audit3000Run, |
| 251 | +}; |
0 commit comments