forgetrail 0.3.0
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.
- package/CONTINUATION_PROMPT.md +42 -0
- package/INITIAL_PROMPT.md +214 -0
- package/LICENSE +201 -0
- package/README.md +198 -0
- package/TRACKING_SCHEMA.md +162 -0
- package/TRY_FORGETRAIL.md +78 -0
- package/WORKFLOW.md +921 -0
- package/content/AGENT_INTEGRATION_claude.md +38 -0
- package/content/AGENT_INTEGRATION_cursor.md +53 -0
- package/content/AGENT_INTEGRATION_generic.md +50 -0
- package/content/AGENT_INTEGRATION_grok.md +82 -0
- package/content/DEV_AUTOMATION_SCRIPTS.md +103 -0
- package/content/FORGETRAIL_LITE.md +1202 -0
- package/content/FORGETRAIL_LITE_UPDATES.md +13 -0
- package/content/FORGETRAIL_PROGRESS.md +26 -0
- package/content/GENESIS_SPEC_PROMPT.md +70 -0
- package/content/GENESIS_STUB.md +18 -0
- package/content/GREENFIELD_INTAKE.md +76 -0
- package/content/KICKOFF_WITHOUT_MCP.md +16 -0
- package/content/LITE_WORKFLOW_TRACKING.json +42 -0
- package/content/NEW_PROJECT_BOOTSTRAP.md +180 -0
- package/content/ONE_CLICK_DEV_SETUP.md +142 -0
- package/content/PLAN_MODE_PATTERNS.md +43 -0
- package/content/POCKETBASE_SCHEMA_SCRIPT.md +52 -0
- package/content/POST_BOOTSTRAP_USER_MESSAGE.md +51 -0
- package/content/SCAFFOLD_INSTALL.json +110 -0
- package/content/SESSION_RESUME_MCP.md +35 -0
- package/content/SYSTEM_HEALTH_CHECKS.md +131 -0
- package/content/USER_REPLY_FORMAT.md +41 -0
- package/content/cursor-rules/forgetrail-lessons-gate.mdc +30 -0
- package/content/cursor-rules/forgetrail-lessons-mcp.mdc +24 -0
- package/content/cursor-rules/forgetrail-no-trailer.mdc +49 -0
- package/content/cursor-rules/forgetrail-phase-status.mdc +61 -0
- package/content/cursor-rules/forgetrail-updates-log.mdc +30 -0
- package/content/cursor-rules/spec-completion.mdc +33 -0
- package/content/cursor-rules/specs-and-todo.mdc +26 -0
- package/content/cursor-rules/url-host-matching.mdc +31 -0
- package/content/cursor-rules/us-english.mdc +9 -0
- package/content/cursor-rules/user-facing-content.mdc +31 -0
- package/content/examples/GENESIS_SAMPLE_mars-habitat-roster.md +154 -0
- package/content/forgetrail-workspace-README.md +43 -0
- package/content/scripts/forgetrail-dev-launcher.mjs +208 -0
- package/content/scripts/forgetrail-env.mjs +51 -0
- package/content/scripts/setup-ollama.mjs +207 -0
- package/content/scripts/setup-pocketbase.mjs +133 -0
- package/content/scripts/test-ollama.mjs +71 -0
- package/content/scripts/test-pocketbase.mjs +110 -0
- package/content/skills/forgetrail/SKILL.md +92 -0
- package/docs/AUTOMATED_TESTING.md +269 -0
- package/docs/BLACK_HAT_REPORT.md +143 -0
- package/docs/BRAND_AND_PRODUCT.md +381 -0
- package/docs/BUGS.md +62 -0
- package/docs/BUSINESS_PLAN.md +256 -0
- package/docs/CODE_QUALITY.md +170 -0
- package/docs/CONTEXT_PROMPT.md +357 -0
- package/docs/DEPLOYMENT.md +181 -0
- package/docs/DESIGN_SYSTEM.md +414 -0
- package/docs/DEV_ESTIMATE.md +127 -0
- package/docs/FORGETRAIL_RENAME.md +193 -0
- package/docs/IDEAS.md +19 -0
- package/docs/MARKETING_GROWTH.md +301 -0
- package/docs/NAMING_EXPLORATION.md +406 -0
- package/docs/NPM.md +98 -0
- package/docs/PHASE_1_BRIEF.md +161 -0
- package/docs/README.md +75 -0
- package/docs/SPEC_FEATURE_TEMPLATE.md +155 -0
- package/docs/SPEC_UI_CHROME_NAV_TEMPLATE.md +48 -0
- package/docs/TECHNICAL_REFERENCE.md +836 -0
- package/docs/TEST_PLAN.md +467 -0
- package/docs/TODO.md +125 -0
- package/package.json +68 -0
- package/prompts/black-hat-audit.md +235 -0
- package/prompts/brand-copy-edit-pass.md +55 -0
- package/prompts/cialdini-marketing-audit.md +141 -0
- package/prompts/competitor-deep-dive.md +201 -0
- package/prompts/docs-alignment-audit.md +108 -0
- package/prompts/engineering-skill-library.md +75 -0
- package/prompts/landing-page-rewrite.md +63 -0
- package/prompts/microcopy-centralization.md +128 -0
- package/prompts/panel-usability-audit.md +197 -0
- package/prompts/personal-beta-outreach.md +195 -0
- package/prompts/pre-launch-audit.md +148 -0
- package/prompts/product-feedback-to-spec.md +76 -0
- package/prompts/propagate-to-forgetrail.md +315 -0
- package/prompts/user-facing-content-sync-audit.md +200 -0
- package/prompts/ux-cohesion-audit.md +165 -0
- package/scripts/ensure-lease.mjs +23 -0
- package/scripts/forgetrail-cli.mjs +85 -0
- package/scripts/install-forgetrail-lite.mjs +11 -0
- package/scripts/install-forgetrail.mjs +11 -0
- package/scripts/install-lib.mjs +115 -0
- package/scripts/install.mjs +163 -0
- package/scripts/link-global.mjs +100 -0
- package/scripts/mcp-lib.mjs +177 -0
- package/scripts/mcp-status.mjs +155 -0
- package/scripts/publish-gate.mjs +110 -0
- package/scripts/rename-to-forgetrail.mjs +273 -0
- package/workflow_tracking.json +124 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# [App Name]: Business Plan
|
|
2
|
+
|
|
3
|
+
_Market analysis, pricing, monetization strategy, customer acquisition, virality playbook, and payment implementation._
|
|
4
|
+
|
|
5
|
+
_For brand positioning, see [BRAND_AND_PRODUCT.md](BRAND_AND_PRODUCT.md). For technical reference, see [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md). For deployment, see [DEPLOYMENT.md](DEPLOYMENT.md)._
|
|
6
|
+
|
|
7
|
+
_Instructions: Write this document when you're thinking about other people using your app (Phase 7: Hardening, or earlier if you need to validate business viability). This is where you prove the business works on paper before building payment flows. Every section should be grounded in numbers, not hopes._
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Market Sizing
|
|
12
|
+
|
|
13
|
+
### Total Addressable Market (TAM)
|
|
14
|
+
|
|
15
|
+
[How many people have the problem you solve? Cite sources.]
|
|
16
|
+
|
|
17
|
+
### Serviceable Addressable Market (SAM)
|
|
18
|
+
|
|
19
|
+
[How many of those people would realistically use a SaaS tool for this? Filter by: tech literacy, willingness to pay, accessibility, geography.]
|
|
20
|
+
|
|
21
|
+
### Initial Target Segment
|
|
22
|
+
|
|
23
|
+
[Who are your first 100 users? Be specific. Where do they hang out? What do they already use?]
|
|
24
|
+
|
|
25
|
+
> 💡 **Lesson learned:** Don't just cite total market numbers. Apply realistic filters. 13-14M total job seekers → only 500K-1M realistically reach a paid SaaS tool for this.
|
|
26
|
+
|
|
27
|
+
## Unit Economics
|
|
28
|
+
|
|
29
|
+
### Cost Per Action
|
|
30
|
+
|
|
31
|
+
[What does it cost you to serve one user action? For AI-powered apps, this is your API cost per call.]
|
|
32
|
+
|
|
33
|
+
| Action | Model/Service | Input Cost | Output Cost | Total Per Action |
|
|
34
|
+
| -------- | ------------- | ---------- | ----------- | ---------------- |
|
|
35
|
+
| [action] | [model] | [$X] | [$X] | [$X] |
|
|
36
|
+
|
|
37
|
+
### Typical User Cost Profile
|
|
38
|
+
|
|
39
|
+
[How much does a typical active user cost you per month?]
|
|
40
|
+
|
|
41
|
+
| Metric | Value |
|
|
42
|
+
| ------------------------------------ | -------- |
|
|
43
|
+
| [Actions] per active user/month | [N] |
|
|
44
|
+
| Average cost per [action] | [$X] |
|
|
45
|
+
| Total API cost per active user/month | [$X] |
|
|
46
|
+
| Infrastructure cost per user/month | [$X] |
|
|
47
|
+
| **Total cost per active user/month** | **[$X]** |
|
|
48
|
+
|
|
49
|
+
### Margin Analysis
|
|
50
|
+
|
|
51
|
+
[At your planned price point, what's the margin?]
|
|
52
|
+
|
|
53
|
+
> 💡 **Lesson learned:** API costs were $0.03/tailoring run, ~$2.50/active user/month. At $119/month pricing, that's 95% margin. Unit economics were bulletproof. Validate this early; if margins are thin, your pricing model needs rethinking before you build payment flows.
|
|
54
|
+
|
|
55
|
+
## Pricing Model
|
|
56
|
+
|
|
57
|
+
### Pricing Philosophy
|
|
58
|
+
|
|
59
|
+
[Why this pricing structure? What behavior does it incentivize? What does it discourage?]
|
|
60
|
+
|
|
61
|
+
> 💡 **Lesson learned:** Monthly-only pricing matched the natural use cycle (6-9 month executive-level job searches). Annual plans would create churn friction.
|
|
62
|
+
>
|
|
63
|
+
> 💡 **Also:** Pure job-count limits feel arbitrary. Hybrid models (credits + job count) let power users self-select into higher tiers without feeling punished.
|
|
64
|
+
>
|
|
65
|
+
> 💡 **Also:** Consider eliminating the free tier entirely. After a time-limited trial (e.g., 7 days of full access), accounts expire to an "expired" state with ALL limits at zero. This forces a clear decision: subscribe, downgrade to Hold, or lose access. A persistent free tier creates a leaky bucket where users never convert. A 30-day data retention window after expiration creates urgency without being punitive. The Hold tier ($10/mo) becomes the only way to preserve data indefinitely without an active subscription.
|
|
66
|
+
>
|
|
67
|
+
> 💡 **Also:** Bridge tiers fill pricing cliffs. If your pricing jumps from a low-cost pause tier ($10/mo) to a full-featured tier ($120/mo), users who've finished their trial but aren't ready for full commitment will churn. A mid-price "bridge" tier ($40/mo) that gives meaningful ongoing value (e.g., profile editing, research, content creation) without the full pipeline (e.g., no per-item AI operations, no export) catches post-trial users and keeps them invested. When they're ready, they upgrade having already built their foundation. Model the bridge tier as: full access to X (the durable value), restricted from Y (the per-action AI cost center).
|
|
68
|
+
>
|
|
69
|
+
> 💡 **Also:** Trial design — usage caps create natural conversion moments. Instead of just a time limit (N days of full access), add a usage cap on the most valuable action (e.g., "download tailored documents for up to 5 items during trial"). Users get enough value to see the product's worth, then hit a clear conversion moment. Track usage with a JSON array field on the user record. Combined with a longer trial (10 days instead of 7), this gives users more time to explore while still creating purchase urgency at the moment they need the product most.
|
|
70
|
+
|
|
71
|
+
### Tiers
|
|
72
|
+
|
|
73
|
+
| Tier | Price | Includes | Target User |
|
|
74
|
+
| -------- | ------- | -------------------------------- | ------------------------------ |
|
|
75
|
+
| Expired | $0 | All blocked | Post-trial default |
|
|
76
|
+
| Hold | $[X]/mo | Data preserved, features blocked | Users who completed their goal |
|
|
77
|
+
| [Tier 2] | $[X]/mo | [includes] | [who this serves] |
|
|
78
|
+
| [Tier 3] | $[X]/mo | [includes] | [who this serves] |
|
|
79
|
+
|
|
80
|
+
### Entitlement Enforcement
|
|
81
|
+
|
|
82
|
+
[Which API routes need to check entitlements? How does enforcement work technically?]
|
|
83
|
+
|
|
84
|
+
> 💡 **Lesson learned:** Entitlements must be enforced on every LLM-consuming API route (12+ routes). A shared middleware or utility function prevents inconsistency. Client-side limits are for UX; server-side limits are for revenue protection.
|
|
85
|
+
|
|
86
|
+
## Customer Acquisition
|
|
87
|
+
|
|
88
|
+
### Channels (Ranked by Expected ROI)
|
|
89
|
+
|
|
90
|
+
1. **[Channel]:** [Why this channel, expected CAC, how to execute]
|
|
91
|
+
2. **[Channel]:** [Why this channel, expected CAC]
|
|
92
|
+
3. **[Channel]:** [Why this channel, expected CAC]
|
|
93
|
+
|
|
94
|
+
> 💡 **Lesson learned:** Pre-launch marketing optimization — run a systematic Cialdini influence audit on your landing page and pricing section before spending on paid channels. The seven principles (Social Proof, Authority, Scarcity, Reciprocity, Commitment/Consistency, Liking, Unity) provide a structured framework for identifying conversion gaps. Key findings:
|
|
95
|
+
>
|
|
96
|
+
> 1. Social Proof: The #1 conversion driver for SaaS. Don't launch with zero proof. Even aggregate counters ("N users have done X this month") work. Title-level specificity ("Built for VPs, Directors, and C-suite") is proof by association.
|
|
97
|
+
> 2. Authority: Domain expertise signals in feature copy (naming specific tools/platforms your audience uses) outperform generic descriptions.
|
|
98
|
+
> 3. Scarcity: For professional audiences, loss-framing ("After trial, AI features lock — but everything you built stays") outperforms urgency timers. Name what stops working, don't just say "Upgrade now."
|
|
99
|
+
> 4. Pricing layout: Cost anchors (competitor pricing, alternative costs) MUST appear above the price grid, not below it. Visitors anchor on the first number they see. Place the expensive reference point before your price.
|
|
100
|
+
>
|
|
101
|
+
> Run the audit as a reusable prompt (see prompts/cialdini-marketing-audit.md if available). Score each principle, prescribe specific copy/UI changes, and prioritize by implementation effort.
|
|
102
|
+
|
|
103
|
+
### CAC/LTV Framework
|
|
104
|
+
|
|
105
|
+
| Metric | Value | Basis |
|
|
106
|
+
| --------------------------- | ---------- | ------------------ |
|
|
107
|
+
| Target CAC | $[X] | [how derived] |
|
|
108
|
+
| Average subscription length | [N] months | [basis] |
|
|
109
|
+
| LTV at [Tier] | $[X] | [price] x [months] |
|
|
110
|
+
| LTV:CAC ratio | [X]:1 | [target >3:1] |
|
|
111
|
+
|
|
112
|
+
## Churn Analysis
|
|
113
|
+
|
|
114
|
+
### Expected Churn Pattern
|
|
115
|
+
|
|
116
|
+
[Is churn a failure signal or a success signal in your business? How does the natural lifecycle of your users affect churn?]
|
|
117
|
+
|
|
118
|
+
> 💡 **Lesson learned:** Job search tool churn is SUCCESS (user landed a job). This reframes economics: every churned user becomes word-of-mouth, a boomerang customer (job changes every 2-3 years), and an emotional testimonial. Anti-churn features should celebrate success, not cling to subscriptions.
|
|
119
|
+
|
|
120
|
+
### Anti-Churn / Re-Engagement
|
|
121
|
+
|
|
122
|
+
[What brings users back? What creates repeat revenue?]
|
|
123
|
+
|
|
124
|
+
> 💡 **Lesson learned:** Consider a low-cost "Hold" or "Pause" tier ($10/mo) for users who complete their goal but may return. Key insights:
|
|
125
|
+
> - Data preservation is the differentiator vs. expiration. Pair hold with a data retention limit on expired accounts (e.g., 30 days inactive → archived). No persistent free tier — expired accounts have zero limits, making Hold the only path to preserve data without a full subscription.
|
|
126
|
+
> - Hold should block all expensive actions (AI, adding new items) but allow viewing everything. Sets maxActiveJobs high (preserve existing) but blocks new creation via a plan-level check.
|
|
127
|
+
> - One-click resume to any paid tier. The resume path should go through Stripe Checkout, not require re-onboarding.
|
|
128
|
+
> - Revenue math: $10/mo × 12 months of hold = $120 from a user who'd otherwise generate $0. If even 20% of churning users choose hold instead of cancel, that's meaningful recurring revenue.
|
|
129
|
+
> - Implementation: same Stripe product/price/webhook pattern as other tiers. Just another entry in PLAN_LIMITS with zeros for all AI counters.
|
|
130
|
+
|
|
131
|
+
## Virality Playbook
|
|
132
|
+
|
|
133
|
+
_Features that create organic sharing should be designed into the product, not bolted on as marketing._
|
|
134
|
+
|
|
135
|
+
### Built-In Sharing Triggers
|
|
136
|
+
|
|
137
|
+
[What in-product moments naturally lead to sharing?]
|
|
138
|
+
|
|
139
|
+
> 📝 **Example:**
|
|
140
|
+
> - "I Landed" celebration creates a shareable moment
|
|
141
|
+
> - Pipeline stats cards are inherently visual/shareable
|
|
142
|
+
> - Resume diff views demonstrate value visually
|
|
143
|
+
|
|
144
|
+
### Network Effects
|
|
145
|
+
|
|
146
|
+
[Does the product get more valuable as more people use it? How?]
|
|
147
|
+
|
|
148
|
+
### Content Virality
|
|
149
|
+
|
|
150
|
+
[Does the product create content that attracts new users?]
|
|
151
|
+
|
|
152
|
+
## Cost Estimates
|
|
153
|
+
|
|
154
|
+
| Category | Monthly Cost | Notes |
|
|
155
|
+
| ---------------------- | ------------------- | ---------------------- |
|
|
156
|
+
| Infrastructure | $[X] | [provider, spec] |
|
|
157
|
+
| API/AI costs | $[X] per [N] users | [model, usage pattern] |
|
|
158
|
+
| Payment processing | [X]% + $[X] per txn | [provider] |
|
|
159
|
+
| Domain/DNS | $[X] | |
|
|
160
|
+
| Monitoring | $[X] | |
|
|
161
|
+
| **Total at [N] users** | **$[X]** | |
|
|
162
|
+
|
|
163
|
+
## Payment Implementation
|
|
164
|
+
|
|
165
|
+
_Technical architecture for Stripe (or alternative). Fill in during Phase 7: Hardening._
|
|
166
|
+
|
|
167
|
+
### Required Schema Changes
|
|
168
|
+
|
|
169
|
+
[What database tables/fields need to exist for billing?]
|
|
170
|
+
|
|
171
|
+
### Stripe Integration Points
|
|
172
|
+
|
|
173
|
+
[Products, prices, checkout sessions, webhooks, customer portal. What needs to be built?]
|
|
174
|
+
|
|
175
|
+
### Promo Code / Discount Strategy
|
|
176
|
+
|
|
177
|
+
> 💡 **Lesson learned:** Promo codes as a growth lever:
|
|
178
|
+
>
|
|
179
|
+
> 1. Use cases to plan for:
|
|
180
|
+
> - Launch discounts (% off first N months)
|
|
181
|
+
> - Referral codes (fixed discount for referrer and referee)
|
|
182
|
+
> - Trial extensions (extra free days for specific audiences)
|
|
183
|
+
> - Partner/influencer codes (tracked via unique codes for attribution)
|
|
184
|
+
> - Win-back codes (for churned users, sent via email campaigns)
|
|
185
|
+
>
|
|
186
|
+
> 2. Unit economics check: Before offering discounts, model the impact.
|
|
187
|
+
> - A 50% discount at $119/mo = $59.50/mo. If API cost is $2.50/user/month, margin is still 96%. Safe.
|
|
188
|
+
> - But a 50% discount at $19/mo = $9.50/mo. If API cost is $5/user/month, margin is 47%. Risky.
|
|
189
|
+
> - Model your worst case: highest-discount code × heaviest-usage user × lowest-price tier.
|
|
190
|
+
>
|
|
191
|
+
> 3. Redemption limits prevent abuse:
|
|
192
|
+
> - Per-code max redemptions (e.g., "first 100 users")
|
|
193
|
+
> - Per-user single-use (promo_redemptions table)
|
|
194
|
+
> - Expiration dates on all promotional codes
|
|
195
|
+
> - Active/inactive flag for immediate kill-switch
|
|
196
|
+
>
|
|
197
|
+
> 4. Stripe mapping: Percent and fixed discounts map to Stripe Promotion Codes. Trial extensions map to Checkout Session trial_period_days. Don't mix — each discount type has its own Stripe mechanism.
|
|
198
|
+
>
|
|
199
|
+
> 5. For technical implementation details, see TECHNICAL_REFERENCE.md > Promo Code System.
|
|
200
|
+
|
|
201
|
+
[What promo code types will you support? What's the discount budget? How will codes be distributed?]
|
|
202
|
+
|
|
203
|
+
### Dynamic Pricing
|
|
204
|
+
|
|
205
|
+
> 💡 **Lesson learned:** Never hardcode prices in UI components. Fetch prices from Stripe at runtime via stripe.prices.retrieve(), cache server-side (e.g., 1 hour TTL), and pass to components through layout data. This lets you change prices in the Stripe Dashboard without redeploying. Components should have hardcoded fallback values for when Stripe is unreachable, but the live values should always come from Stripe.
|
|
206
|
+
|
|
207
|
+
[How will display prices be kept in sync with Stripe? Options: (1) fetch from Stripe and cache server-side (recommended), (2) public env vars (requires restart), (3) shared config file (requires redeploy).]
|
|
208
|
+
|
|
209
|
+
### Implementation Phases
|
|
210
|
+
|
|
211
|
+
[Break payment work into sequential phases with dependencies.]
|
|
212
|
+
|
|
213
|
+
## Product Analytics & Success Metrics
|
|
214
|
+
|
|
215
|
+
_Instrument your app to prove it works. Without analytics, you're guessing._
|
|
216
|
+
|
|
217
|
+
> 💡 **Lesson learned:** PostHog Cloud (free tier: 1M events/month) is the right tool for early-stage product analytics. It covers funnels, retention cohorts, feature adoption, and session replay in one tool. Plausible/Umami are web analytics (pageviews) — they don't give you funnels or custom events. Rolling your own event capture is easy; building the analysis UI is weeks of work.
|
|
218
|
+
>
|
|
219
|
+
> Key pattern: Create a thin wrapper module ($lib/posthog.ts) that exports initPostHog(), identifyUser(), trackEvent(), trackPageView(). Initialize in root layout onMount. If the API key isn't set, all calls silently no-op (safe for local dev). Track SPA page views via afterNavigate, not PostHog's automatic capture (which double-counts in SPA frameworks).
|
|
220
|
+
|
|
221
|
+
### SM0: Does The Product Work?
|
|
222
|
+
|
|
223
|
+
[Define your core workflow funnel. What sequence of actions proves a user got value? Instrument each step.]
|
|
224
|
+
|
|
225
|
+
| Event | When It Fires | What It Proves |
|
|
226
|
+
| ------------ | ------------- | -------------------------- |
|
|
227
|
+
| [event_name] | [trigger] | [what this step validates] |
|
|
228
|
+
|
|
229
|
+
### SM1: Net Promoter Score
|
|
230
|
+
|
|
231
|
+
[When will you survey users? At what product moments? How will you use qualitative feedback?]
|
|
232
|
+
|
|
233
|
+
> 💡 **Lesson learned:** NPS is most useful when triggered at moments of realized value, not random intervals.
|
|
234
|
+
>
|
|
235
|
+
> Implementation pattern:
|
|
236
|
+
> 1. Identify 2-3 "value moments" — points where the user has received enough value to form an opinion (e.g., 3rd use of a core feature, first success outcome). Don't survey on first use.
|
|
237
|
+
> 2. Build an in-app toast/bottom-sheet, NOT an email. Email NPS has low response rates. In-app catches the user in context.
|
|
238
|
+
> 3. Standard 0-10 scale → follow-up qualitative question. Tailor the question to the score:
|
|
239
|
+
> - Detractors (0-6): "What's not working for you?"
|
|
240
|
+
> - Passives (7-8): "What would make this a 10?"
|
|
241
|
+
> - Promoters (9-10): "What do you love most?"
|
|
242
|
+
> 4. Store responses in a dedicated collection (user, score, reason, trigger, created). Make records immutable (no update/delete) for audit integrity.
|
|
243
|
+
> 5. Wire triggers via window events (CustomEvent) so components stay decoupled. The NPS component listens; feature components just dispatch events when milestones hit.
|
|
244
|
+
> 6. Check server-side whether the user already responded for a given trigger before showing. One survey per trigger per user.
|
|
245
|
+
> 7. Track nps_submitted and nps_dismissed in PostHog for response rate analysis.
|
|
246
|
+
> 8. Target: NPS ≥ 40 before investing in retention/growth (SM2+).
|
|
247
|
+
|
|
248
|
+
### SM2: Retention
|
|
249
|
+
|
|
250
|
+
[What does healthy retention look like for your product? Is churn a failure signal or a success signal?]
|
|
251
|
+
|
|
252
|
+
### SM3: CAC Doubling Time
|
|
253
|
+
|
|
254
|
+
[How will you track acquisition cost and viral loops? What's your K-factor formula?]
|
|
255
|
+
|
|
256
|
+
> 🔧 **Guidance:** These Success Metrics (SM0-SM4) are from Pablo Cruz's Venture Building framework (via Tim Connors, ex-Sequoia). They're sequential: don't optimize NPS before the product works, don't pour money into CAC before retention proves the product holds users. See: https://www.pscruz.com/p/venture-building-success-metrics
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Code Quality Review
|
|
2
|
+
|
|
3
|
+
_Deep review of the codebase covering security, type safety, error handling, reactivity, performance, and consistency. Findings are rated Critical, Major, or Minor. Ask Claude to generate this during Phase 7: Hardening._
|
|
4
|
+
|
|
5
|
+
_For exploitable security vulnerabilities (attacker-focused), see [BLACK_HAT_REPORT.md](BLACK_HAT_REPORT.md). For feature documentation see [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md). For deployment readiness see [DEPLOYMENT.md](DEPLOYMENT.md)._
|
|
6
|
+
|
|
7
|
+
_Instructions: Ask Claude to produce this document by running a systematic audit: "Audit the codebase for production readiness. Organize findings by category, rate them Critical/Major/Minor, and include specific file:line references with remediation steps." Update it as findings are fixed._
|
|
8
|
+
|
|
9
|
+
_Note: This document covers **engineering quality** (type safety, error handling, consistency, maintainability). For **security vulnerabilities** (injection, IDOR, auth bypass, SSRF), use the black hat audit prompt (`_forgetrail/prompts/black-hat-audit.md`) and save results to [BLACK_HAT_REPORT.md](BLACK_HAT_REPORT.md). The two reports complement each other — some findings may appear in both with different framing._
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Audit Categories
|
|
14
|
+
|
|
15
|
+
> 🔧 **Guidance:** These categories emerged from Exec Foundry's audit. Not all will apply to every project, but they're a solid starting checklist.
|
|
16
|
+
>
|
|
17
|
+
> Key lessons learned:
|
|
18
|
+
> 1. FIELD SIZE LIMITS: Check all LLM output against database field size limits. PocketBase's default 5K text limit silently truncated LLM output with no error. Ask Claude: "Check all LLM function calls and their output sizes against field limits."
|
|
19
|
+
> 2. SILENT FAILURES: Grep for catch blocks that return empty results. Functions that catch errors and return [] or null are invisible bugs.
|
|
20
|
+
> 3. ERROR FORMAT CONSISTENCY: All API routes should return errors in the same format. Mixed { error } and { message } formats cause client-side handling bugs.
|
|
21
|
+
> 4. TYPE SAFETY: Count `as any` casts. Each one is a potential silent failure when schemas change. LLM JSON responses need runtime validation. Prefer systematic replacement: **`unknown` at catch boundaries and JSON edges**, **typed mapper inputs** (e.g. PocketBase `RecordModel` instead of `as any`), and **literal unions** for UI state instead of widening to `any` for event handlers.
|
|
22
|
+
>
|
|
23
|
+
> 5. **LLM JSON parse hardening (verbatim-copy prompts):** Do not use bare `JSON.parse` on model output when strings contain user document text. Use a shared **`parseJsonFromLlmOutput`** (fence strip + leading-object extraction + **control-char sanitization inside string literals** as fallback). Strict parse first so valid output is unchanged. Add unit tests for raw-newline-in-string payloads.
|
|
24
|
+
> 5. AUTHORIZATION: Check that auth checks are atomic. A delegation check followed by a separate data fetch can be exploited between the two calls.
|
|
25
|
+
|
|
26
|
+
## Summary Table
|
|
27
|
+
|
|
28
|
+
| Category | Critical | Major | Minor | Fixed |
|
|
29
|
+
| ---------------- | -------- | ----- | ----- | ----- |
|
|
30
|
+
| Security | | | | |
|
|
31
|
+
| Type Safety | | | | |
|
|
32
|
+
| Error Handling | | | | |
|
|
33
|
+
| Reactivity/State | | | | |
|
|
34
|
+
| Performance | | | | |
|
|
35
|
+
| Consistency | | | | |
|
|
36
|
+
| **Total** | | | | |
|
|
37
|
+
|
|
38
|
+
## Critical Findings
|
|
39
|
+
|
|
40
|
+
_Must fix before launch. Each finding includes: what's wrong, where it is, why it matters, and how to fix it._
|
|
41
|
+
|
|
42
|
+
### C1: [Finding Title]
|
|
43
|
+
|
|
44
|
+
**File:** `[path/to/file.ts]:[line]`
|
|
45
|
+
**Category:** [Security / Type Safety / etc.]
|
|
46
|
+
**Impact:** [What could go wrong]
|
|
47
|
+
|
|
48
|
+
**Problem:**
|
|
49
|
+
[Specific description with code example if helpful]
|
|
50
|
+
|
|
51
|
+
**Fix:**
|
|
52
|
+
[Exact remediation steps]
|
|
53
|
+
|
|
54
|
+
**Status:** [ ] Not started / [x] Fixed ([date])
|
|
55
|
+
|
|
56
|
+
## Major Findings
|
|
57
|
+
|
|
58
|
+
_Should fix before launch. Won't cause security incidents but will cause user-facing bugs or maintenance pain._
|
|
59
|
+
|
|
60
|
+
### M1: [Finding Title]
|
|
61
|
+
|
|
62
|
+
**File:** `[path/to/file.ts]:[line]`
|
|
63
|
+
**Category:** [category]
|
|
64
|
+
**Impact:** [What could go wrong]
|
|
65
|
+
|
|
66
|
+
**Problem:**
|
|
67
|
+
[Description]
|
|
68
|
+
|
|
69
|
+
**Fix:**
|
|
70
|
+
[Remediation]
|
|
71
|
+
|
|
72
|
+
**Status:** [ ] Not started / [x] Fixed ([date])
|
|
73
|
+
|
|
74
|
+
## Minor Findings
|
|
75
|
+
|
|
76
|
+
_Fix when convenient. Code quality improvements that reduce future maintenance burden._
|
|
77
|
+
|
|
78
|
+
### m1: [Finding Title]
|
|
79
|
+
|
|
80
|
+
**File:** `[path/to/file.ts]:[line]`
|
|
81
|
+
**Category:** [category]
|
|
82
|
+
|
|
83
|
+
**Problem:**
|
|
84
|
+
[Description]
|
|
85
|
+
|
|
86
|
+
**Fix:**
|
|
87
|
+
[Remediation]
|
|
88
|
+
|
|
89
|
+
**Status:** [ ] Not started / [x] Fixed ([date])
|
|
90
|
+
|
|
91
|
+
## Priority Actions
|
|
92
|
+
|
|
93
|
+
_Sequenced by dependency. Do these in order._
|
|
94
|
+
|
|
95
|
+
1. [ ] [Action]: [Which findings it resolves]
|
|
96
|
+
2. [ ] [Action]: [Which findings it resolves]
|
|
97
|
+
3. [ ] [Action]: [Which findings it resolves]
|
|
98
|
+
|
|
99
|
+
## Audit Checklist for Future Reviews
|
|
100
|
+
|
|
101
|
+
_Run through this list periodically, especially before releases._
|
|
102
|
+
|
|
103
|
+
- [ ] Grep for `as any` casts and evaluate each one
|
|
104
|
+
- [ ] Grep for `catch (` / `: any)` — prefer `unknown` plus shared helpers for user-facing messages and optional HTTP/status extraction
|
|
105
|
+
- [ ] Confirm lint/typecheck (e.g. ESLint with `@typescript-eslint/no-explicit-any`, `svelte-check`) runs in CI or pre-release so type hygiene doesn’t regress silently
|
|
106
|
+
- [ ] Grep for empty catch blocks (`catch (e) { }` or `catch { return [] }`)
|
|
107
|
+
- [ ] Check all API routes return errors in the same format
|
|
108
|
+
- [ ] Verify all LLM output fields have adequate database field sizes
|
|
109
|
+
- [ ] Check all auth/permission checks are atomic (no TOCTOU vulnerabilities)
|
|
110
|
+
- [ ] Verify all setTimeout/setInterval are cleaned up on component unmount
|
|
111
|
+
- [ ] Check for stale closure captures in async functions with reactive state
|
|
112
|
+
- [ ] Validate all user input before database queries (injection prevention)
|
|
113
|
+
- [ ] Verify error messages are user-facing, not developer-facing
|
|
114
|
+
- [ ] **User-facing copy (when adopted):** run `export:copy`; `audit:inline-copy` at **0 UI prose not in export**; run `user-facing-content-sync-audit.md` before major marketing pushes; grep `\bcan\b` for capability hedges (`can strengthen` → `strengthens`) per **`.cursor/rules/user-facing-content.mdc`**
|
|
115
|
+
- [ ] Check that no secrets are hardcoded or logged
|
|
116
|
+
- [ ] Verify all database filter strings use parameterized helpers (no string interpolation)
|
|
117
|
+
- [ ] Check that promo code redemption and usage counter increments are atomic
|
|
118
|
+
- [ ] Verify delegation/impersonation routes check access level before mutations
|
|
119
|
+
- [ ] Check that rate limiting covers all expensive endpoints (LLM calls, scraping, file processing)
|
|
120
|
+
- [ ] Verify untrusted content (user input, scraped pages, uploads) is fenced with an injection-guard wrapper before entering any LLM prompt
|
|
121
|
+
- [ ] Verify LLM calls read all response text blocks (not `content[0]`), disable thinking for JSON tasks, and treat blank model-name env vars as unset
|
|
122
|
+
- [ ] Verify Stripe webhook signatures are validated on all payment endpoints
|
|
123
|
+
- [ ] Verify all `fetch` call sites check `res.ok` before calling `.json()` or assigning response data to state
|
|
124
|
+
- [ ] Check that all `setInterval` polling loops handle 429 responses with exponential backoff (not just `!res.ok` → skip)
|
|
125
|
+
- [ ] Verify optimistic UI updates (star toggles, status changes) revert on `!res.ok`, not only on thrown errors
|
|
126
|
+
- [ ] Check that success indicators ("Saved", "Deleted", dialog closes) are gated on `res.ok`, not fired unconditionally
|
|
127
|
+
- [ ] Verify async callbacks in long-running operations guard against stale context (entity ID changed during await)
|
|
128
|
+
- [ ] Check that bulk/batch operations suppress per-item polling (no N×M interval storm)
|
|
129
|
+
- [ ] Verify that third-party file parsing engines (such as `pdf-parse` or `mammoth`) are isolated behind a centralized server-side file utility boundary (`pbFiles.ts` or similar) to protect route endpoints from library-specific imports.
|
|
130
|
+
- [ ] Check that all ISO timestamp string transformations are standardized using a shared formatter (`formatUtcTimestamp` or similar) rather than repeated ad-hoc string replacements.
|
|
131
|
+
- [ ] Verify that all gated admin-only API endpoints proxying private edge nodes enforce strict admin checks (`requireAdmin`) and implement a short connection timeout to prevent hanging.
|
|
132
|
+
- [ ] **URL host classification:** Grep for `hostname.includes('…com')` / `url.includes('…com')` used as board/ATS allowlists — prefer a shared `hostMatchesDomain` (exact or subdomain suffix match on parsed hostname), not substring checks (CodeQL `js/incomplete-url-substring-sanitization`).
|
|
133
|
+
- [ ] **SSRF boundary:** Outgoing fetch / browser navigation validates http(s) allowlist, rejects userinfo, localhost, private IPs, and `.local`/`.internal` at the handler edge; re-check redirects.
|
|
134
|
+
- [ ] **Address ranges are numeric, not string prefixes:** blocked ranges computed from octets/hextets (whole `127/8`, not `=== '127.0.0.1'`), IPv6 expanded before comparison (`::ffff:127.0.0.1` normalizes to `::ffff:7f00:1`), hostnames lowercased with brackets and the trailing FQDN dot stripped (`localhost.` is `localhost`).
|
|
135
|
+
- [ ] **Guards have regression tests:** each blocked input class is asserted in a test that fails against the pre-fix code, and each branch of the guard is proven reachable (a regex that can never match is dead code, not a defense).
|
|
136
|
+
- [ ] **Escaping matches the downstream parser:** compare against the vendor SDK's own escape helper before hand-rolling (e.g. PocketBase filters use backslash escapes, not SQL-style doubled quotes).
|
|
137
|
+
- [ ] **Sanitizers run in one pass:** chained `.replace()` calls re-scan their own output (an entity decoder that expands `&` before named entities still double-decodes); use a single regex pass, or iterate to a fixed point when stripping nested constructs.
|
|
138
|
+
- [ ] **Validate-then-connect is closed, not just narrowed:** the check and the socket must use the same address. In Node, an `undici` `Agent` with a filtering `connect.lookup` removes the rebinding window that a `resolve → validate → fetch` sequence leaves open; honor `options.all` so Happy Eyeballs still works.
|
|
139
|
+
- [ ] **Headless browser navigation is guarded too:** a validated `goto` URL does not constrain 3xx, `<meta refresh>`, or script navigation. Abort navigation requests to blocked hosts via request interception and re-check the landed URL before reading content. Note the residual limit: the browser resolves DNS itself unless routed through a controlled proxy.
|
|
140
|
+
- [ ] **Scheme allowlist:** Prefer `protocol === 'http:' || protocol === 'https:'` on a parsed `URL`, not only denying `javascript:`.
|
|
141
|
+
- [ ] **GitHub Actions:** Workflows set top-level `permissions:` (least privilege). Never return `err.stack` / raw exception text in client JSON.
|
|
142
|
+
|
|
143
|
+
> 💡 **Lesson learned:** **Host matching is not string `includes`.** Scrapers and board classifiers that use `hostname.includes('linkedin.com')` match lookalikes and generate dozens of identical CodeQL alerts. One shared helper (`host === domain || host.endsWith('.' + domain)`) plus call-site migration clears the storm and keeps regional subdomains (`uk.linkedin.com`, `boards.greenhouse.io`) working. Keep path checks on `pathname`. Sibling edge services that cannot import the app module should copy the helper with a sync comment.
|
|
144
|
+
>
|
|
145
|
+
> 📝 **Example:** `hostMatchesDomain('www.linkedin.com', 'linkedin.com')` → true; `hostMatchesDomain('evil-linkedin.com', 'linkedin.com')` → false.
|
|
146
|
+
|
|
147
|
+
> 💡 **Lesson learned:** **A hardening fix is not done until you run the bypass inputs through it.** A codebase can pass a security audit, ship the recommended guard, and still be exploitable because the guard compares strings where it should compare ranges. Real examples from one Exec Foundry sweep: an SSRF check that blocked `127.0.0.1` but not `127.0.0.2` or `[::ffff:127.0.0.1]` or `localhost.`; a DNS-resolution check whose IPv4-mapped branch used `/^:ffff:/` and could never match the resolver's `::ffff:` output; a filter escaper using SQL-style doubled quotes against a parser that expects backslashes; and an "`&` last" entity decoder that still double-decoded because numeric refs ran first. All four had been reviewed and credited as effective. Add an **AREA 0 fix-efficacy pass** to security re-audits, and land a regression test with every guard so the next refactor cannot quietly undo it.
|
|
148
|
+
>
|
|
149
|
+
> 📝 **Example:** `assert.throws(() => assertSafeUrl('http://[::ffff:169.254.169.254]/'))` is worth more than a paragraph in the audit report claiming metadata endpoints are blocked.
|
|
150
|
+
|
|
151
|
+
> 💡 **Lesson learned:** **Tighten types at boundaries instead of sprinkling `any`.** Unchecked `any` makes refactors expensive: renamed API fields and mapper outputs fail silently until a user hits an edge case. Treat **`catch` clauses**, **`fetch().json()` results**, and **ORM/PocketBase rows passed into mappers** as the main places to invest — use `unknown`, narrow once, or cast to the framework’s record type (`RecordModel`) at the mapper input so greps stay honest. For UI toggles and segmented controls, model allowed values as a string union (or `typeof` existing state) instead of `as any` on click handlers.
|
|
152
|
+
>
|
|
153
|
+
> 📝 **Example:** A shared `errorMessage(e: unknown)` (and optional `getHttpStatus`) avoids duplicating `instanceof Error` checks across dozens of Svelte components; `pbEntityToApp(record as RecordModel)` documents the handoff from raw store to domain type better than `record as any`.
|
|
154
|
+
|
|
155
|
+
> 💡 **Lesson learned:** Remediation tracking pattern — integrating findings with TODO:
|
|
156
|
+
>
|
|
157
|
+
> 1. After generating CODE_QUALITY.md and BLACK_HAT_REPORT.md, add ALL actionable findings to TODO.md under dedicated sections.
|
|
158
|
+
> 2. Use priority tiers:
|
|
159
|
+
> - P0 (Critical/Immediate): Directly exploitable vulnerabilities, data loss risks. Fix before launch.
|
|
160
|
+
> - P1 (High/Short-term): Defense-in-depth, hardening, significant code quality issues. Fix next sprint.
|
|
161
|
+
> - P2 (Medium-Low/Backlog): Nice-to-haves, future-proofing, cosmetic code quality. Schedule when convenient.
|
|
162
|
+
> 3. Annotate each finding in the report with its TODO status: `→ *Added to TODO (P0)*`
|
|
163
|
+
> 4. As fixes are implemented, update BOTH:
|
|
164
|
+
> - The finding's status in this doc: `**Status:** [x] Fixed (date)`
|
|
165
|
+
> - The TODO checkbox
|
|
166
|
+
> 5. This dual-tracking prevents findings from getting lost and provides an audit trail of what was fixed and when.
|
|
167
|
+
|
|
168
|
+
> 💡 **Lesson learned:** **Audit for fetch/polling resilience as a class, not one-off.** After discovering that bulk import triggered a 429 request storm (N concurrent polling intervals × rate-limited API = frozen UI + cascading `TypeError`s from error objects assigned to array state), a systematic sweep of every `setInterval`-based polling loop and every `fetch` → `.json()` call site revealed the same anti-pattern in 6+ files: no `res.ok` guard, no 429-specific backoff, no timer cleanup on destroy. Treat these as a **category** during code review — anywhere `setInterval` + `fetch` appear together, verify the three guards (response check, backoff, lifecycle cleanup). Bulk operations are the stress test: if single-item import works fine, try 20+ items and watch the console for 429 cascades.
|
|
169
|
+
>
|
|
170
|
+
> 📝 **Example:** A skip-tick backoff pattern for `setInterval` polling: declare `let skipTicks = 0` outside the interval callback; on 429, set `skipTicks = Math.min((skipTicks || 1) * 2, maxSkip)`; at the start of each tick, if `skipTicks > 0`, decrement and return. This provides exponential backoff within the existing interval without replacing it with `setTimeout` chains.
|