diffmode 0.1.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.
@@ -0,0 +1,118 @@
1
+ # Exit codes reference
2
+
3
+ `diffmode`'s exit codes are part of the public contract — agents and shell
4
+ scripts branch on them. The table below is verbatim from spec §8.
5
+
6
+ | Code | Name | Trigger | Recovery |
7
+ | --- | --- | --- | --- |
8
+ | `0` | OK | Command succeeded. | None. |
9
+ | `1` | GENERIC | Unclassified failure. | Surface stderr `error.message` to the user. |
10
+ | `2` | USAGE | Wrong flags, missing args, schema violations, "module not resumable", "no prior free-tier", … | Read stderr; fix the invocation. |
11
+ | `3` | NETWORK | DNS, TLS, connection-reset, response-body parse failure. | Check connectivity; retry with backoff (≤ 3 attempts). |
12
+ | `4` | AUTH | No token, expired/revoked PAT, or 401 from server. | Ask the user to run `diffmode login`, then retry. |
13
+ | `5` | CONFLICT (409) | A job is already running for this product. | Parse stderr `error.job_id`; switch to `diffmode jobs watch <id>`. |
14
+ | `6` | NOT_FOUND | 404 from server (unknown job id, missing product). | Verify the id/product slug. |
15
+ | `7` | RATE_LIMITED (429) | Free-tier limit (3 submits / 24 h) or `Retry-After` set. | Honor `error.retry_after` (seconds); sleep and retry. |
16
+ | `8` | INSUFFICIENT_CREDITS (402) | Pre-flight or server rejected the submit. | Print `error.billing_url`; instruct the user to top up at `https://diffmode.app/app/billing`. **Do not retry.** |
17
+ | `9` | SERVER | 5xx. | If `error.retryable === true`, retry with backoff. Otherwise stop and surface. |
18
+ | `10` | INTERRUPTED_RESUMABLE | Server marked the job interrupted (deploy/rotation). | Run `diffmode jobs resume <id>` once, then `diffmode jobs watch <id>` again. |
19
+ | `130` | SIGINT | User hit Ctrl-C. | Print resume hint (the server job is still running); do NOT auto-cancel. |
20
+
21
+ ## Per-code recovery scripts
22
+
23
+ ### Exit 4 (AUTH)
24
+
25
+ ```bash
26
+ diffmode whoami --json
27
+ # exit 4 → prompt user
28
+ echo "Please authenticate, then re-run."
29
+ echo " npx diffmode@latest login (paste PAT from https://diffmode.app/app/tokens)"
30
+ exit 4
31
+ ```
32
+
33
+ ### Exit 5 (CONFLICT — in-flight job)
34
+
35
+ The error envelope on stderr carries the running `job_id`:
36
+
37
+ ```json
38
+ {"error": {"code": "conflict", "message": "...", "retryable": false, "job_id": "<id>", "product_id": "<p>"}}
39
+ ```
40
+
41
+ Pivot to watching that job:
42
+
43
+ ```bash
44
+ diffmode run "$PRODUCT" --input founder.json --idempotency-key "$UUID" --json
45
+ # if exit 5:
46
+ JOB_ID=$(jq -r '.error.job_id' /tmp/stderr.json)
47
+ diffmode jobs watch "$JOB_ID" --json --wait 4h
48
+ ```
49
+
50
+ ### Exit 7 (RATE_LIMITED)
51
+
52
+ Read `error.retry_after` (seconds). If ≤ 300, sleep and retry once. If
53
+ larger, surface to the user — don't loop silently.
54
+
55
+ ### Exit 8 (INSUFFICIENT_CREDITS)
56
+
57
+ Browser-only top-up. The CLI **never** calls `POST /billing/checkout`. The
58
+ error payload includes the billing URL the CLI resolves at runtime:
59
+
60
+ ```json
61
+ {"error": {"code": "insufficient_credits", "message": "...", "retryable": false, "billing_url": "https://diffmode.app/app/billing"}}
62
+ ```
63
+
64
+ Tell the user, do not retry:
65
+
66
+ ```
67
+ You're out of credits. Top up at https://diffmode.app/app/billing,
68
+ then re-run `diffmode run <product>`.
69
+ ```
70
+
71
+ ### Exit 10 (INTERRUPTED_RESUMABLE)
72
+
73
+ Workflow + free-tier are resumable; idea-eval, smoke-test, and unlock are
74
+ not. `diffmode jobs resume` handles the module branching for you:
75
+
76
+ ```bash
77
+ diffmode jobs resume "$JOB_ID" --json
78
+ diffmode jobs watch "$JOB_ID" --json --wait 4h
79
+ ```
80
+
81
+ If `jobs resume` itself exits 2 with "module not resumable", resubmit:
82
+
83
+ ```bash
84
+ diffmode "$MODULE" "$PRODUCT" --input founder.json --idempotency-key "$NEW_UUID"
85
+ ```
86
+
87
+ ### Exit 130 (SIGINT)
88
+
89
+ The server-side job keeps running. Confirm with the user; if they want to
90
+ keep going:
91
+
92
+ ```bash
93
+ diffmode jobs watch "$JOB_ID" --json --wait 4h
94
+ ```
95
+
96
+ Do NOT issue `DELETE /jobs/{id}` on Ctrl-C — the CLI deliberately skips
97
+ that to keep jobs durable across watch interruptions.
98
+
99
+ ## Coverage per command
100
+
101
+ The exact exit-code set per command is in `commands.md` (and emitted by
102
+ `diffmode commands --json` under each entry's `exits` field). This file
103
+ documents the codes themselves; that file documents which commands can emit
104
+ which codes.
105
+
106
+ ## Drift policy
107
+
108
+ This table mirrors `src/lib/exit-codes.ts` (constants) and spec §8 (the
109
+ authoritative source). If you change a code's number or name:
110
+
111
+ 1. Update `src/lib/exit-codes.ts`.
112
+ 2. Update the constant test in `test/exit-codes.test.ts`.
113
+ 3. Update this file + `commands.md`.
114
+ 4. Bump `schema_version` if the JSON envelope's `error.code` strings
115
+ change semantically.
116
+
117
+ The CI matrix in `test/exit-codes.test.ts` enforces constants vs. spec
118
+ parity; this doc is for humans + agents.
@@ -0,0 +1,113 @@
1
+ # Founder input schema (`FounderDiagnostics`)
2
+
3
+ `diffmode run`, `diffmode workflow`, and `diffmode smoke-test` all accept a
4
+ founder-input JSON object via `--input <file|->`. The schema mirrors the
5
+ backend Pydantic class `FounderDiagnostics` in the upstream Diffmode API
6
+ (`src/api/models.py`).
7
+
8
+ > **Drift policy:** regenerate this file whenever
9
+ > `models.py:FounderDiagnostics` changes. Treat the Pydantic class as the
10
+ > source of truth; this document is a developer-facing mirror. The CLI
11
+ > parser intentionally tolerates unknown keys (`extra="allow"`) so adding
12
+ > a field to the backend never breaks an existing client.
13
+
14
+ ## Required
15
+
16
+ - **`product_description`** *(string, non-empty)* — what the product does.
17
+ This is the only hard-required field. Missing it → CLI exits 2 with a
18
+ pointer to this file.
19
+
20
+ ## Recommended (warn if missing)
21
+
22
+ These fields meaningfully sharpen the generated plan; the CLI prints a
23
+ non-fatal stderr warning when they're absent.
24
+
25
+ - `target_audience` *(string)* — current or target customers.
26
+ - `trigger_events` *(string)* — moments customers need the solution.
27
+ - `pricing` *(string)* — business model and pricing.
28
+ - `acquisition_sources` *(string)* — where customers come from today.
29
+ - `current_growth` *(object|null)* — last-30-days traffic + conversion.
30
+
31
+ ## Optional — diagnostics (v2.2)
32
+
33
+ - `alternatives_used` *(string)*
34
+ - `marketing_experiments` *(string)*
35
+ - `tactics_ruled_out` *(string)*
36
+ - `goals` *(string)*
37
+ - `budget` *(string)* — monthly budget shorthand.
38
+ - `product_complexity` *(object|null)* — `{ type, details }`. `from-url`
39
+ pre-fill populates this when the analysis returns a complexity hint.
40
+ - `resource_constraints` *(object|null)*
41
+ - `problem_urgency` *(object|null)*
42
+ - `challenges` *(object|null)*
43
+
44
+ ## Optional — business / team context
45
+
46
+ - `business_model` *(string)*
47
+ - `current_mrr` *(string)*
48
+ - `funding_stage` *(string)*
49
+ - `team_size` *(string)*
50
+ - `your_role` *(string)*
51
+ - `marketing_team_size` *(string)*
52
+ - `resource_profile` *(string)* — `"solo"` / `"small_team"` / `"dedicated"`.
53
+
54
+ ## Optional — persona & customer mix
55
+
56
+ - `persona_type` *(string)* — founder vs marketing-hire framing.
57
+ - `observed_customers` *(string)*
58
+ - `customer_mix` *(string)*
59
+ - `top_competitors` *(string)*
60
+ - `what_makes_you_different` *(string)*
61
+ - `channels_tried_raw` *(string | string[])*
62
+
63
+ ## Optional — growth & retention signals
64
+
65
+ - `retention_signal` *(string)*
66
+ - `emotional_signals` *(string)*
67
+ - `blind_spots` *(string)*
68
+ - `comprehension_friction` *(string)*
69
+ - `comfortable_with_outreach` *(`""` | `"yes"` | `"no"`)*
70
+ - `trigger_events_source` *(string)*
71
+
72
+ ## Extra keys
73
+
74
+ The backend accepts arbitrary additional keys (`extra="allow"`). The
75
+ parent diagnostics formatter renders them under an "Additional Context"
76
+ section. **Reserved keys** (`user_id`, `created_at`, `updated_at`, `id`,
77
+ `job_id`, `product_id`) are server-managed and rejected by the CLI before
78
+ submit.
79
+
80
+ ## Example
81
+
82
+ ```json
83
+ {
84
+ "product_description": "Drop-in fraud-detection API for fintech apps.",
85
+ "pricing": "Usage-based — $0.002/request after a 10k-request free tier.",
86
+ "target_audience": "Series-A fintechs adding their first underwriting team.",
87
+ "trigger_events": "First fraud incident or compliance review.",
88
+ "acquisition_sources": "Founder outbound + a small founder-led community.",
89
+ "current_growth": { "traffic_30d": 12000, "conversion_rate": 0.018 },
90
+ "goals": "$50k MRR in 6 months."
91
+ }
92
+ ```
93
+
94
+ ## Pre-fill from a URL
95
+
96
+ `diffmode diagnostics from-url https://your-site.example` calls
97
+ `POST /public/v1/analyze-website` and emits a draft `FounderDiagnostics`
98
+ JSON. Field mapping:
99
+
100
+ | Response field | FounderDiagnostics field |
101
+ |-------------------------------------------|-----------------------------------|
102
+ | `company_description` | `product_description` |
103
+ | `analysis_pricing` | `pricing` |
104
+ | `target_customer` | `target_audience` |
105
+ | `analysis_trigger_events` | `trigger_events` |
106
+ | `how_customers_find_you_today` | `acquisition_sources` |
107
+ | `top_competitors` | `top_competitors` |
108
+ | `what_makes_you_different` | `what_makes_you_different` |
109
+ | `analysis_product_complexity_type` + `_details` | `product_complexity` *(object)* |
110
+
111
+ `auto_filled_fields` / `draft_fields` from the response are logged to
112
+ stderr (not part of the diagnostics object) so you can tell which fields
113
+ are confident vs. best-guess. Always review draft fields before submit.
@@ -0,0 +1,82 @@
1
+ # IdeaInput schema
2
+
3
+ The `diffmode idea-eval` command accepts a structured JSON list of `IdeaInput`
4
+ objects, not free-text strings. This file mirrors the canonical Pydantic
5
+ shape declared in the upstream Diffmode API (`IdeaInput` in `src/api/models.py`,
6
+ ~lines 43–83) — regenerate this doc when that class changes.
7
+
8
+ ## Top-level shape
9
+
10
+ The ideas file passed via `--ideas-file <path>` MUST be a JSON array of
11
+ `IdeaInput` objects:
12
+
13
+ ```json
14
+ [
15
+ { "name": "…", "description": "…", … },
16
+ { "name": "…", "description": "…", … }
17
+ ]
18
+ ```
19
+
20
+ A single-object file (`{"ideas": [...]}`) is **not** accepted — the array
21
+ must be the top-level value.
22
+
23
+ ## Required fields
24
+
25
+ | Field | Type | Notes |
26
+ | --- | --- | --- |
27
+ | `name` | string (non-empty) | Idea name |
28
+ | `description` | string (non-empty) | One-sentence description |
29
+
30
+ ## Optional fields
31
+
32
+ All optional fields default to `""` server-side and are forwarded into the
33
+ "Additional Context" section of the formatted prompt.
34
+
35
+ | Field | Type | Description |
36
+ | --- | --- | --- |
37
+ | `target_customer` | string | Target segment, B2B/B2C, ideal first 10 customers |
38
+ | `problem` | string | Pain point being solved, urgency, consequence of inaction |
39
+ | `solution` | string | Solution approach, how it solves, differentiation |
40
+ | `revenue_model` | string | Subscription, one-time, marketplace, etc. |
41
+ | `price_point` | string | Price point |
42
+ | `revenue_goal_min` | string | Minimum viable MRR + timeline |
43
+ | `revenue_goal_ambitious` | string | Ambitious revenue goal |
44
+ | `validation` | string | Existing validation: conversations, signups, prototypes |
45
+
46
+ ## Extras
47
+
48
+ `IdeaInput` uses Pydantic `extra="allow"` — any additional fields you include
49
+ will be preserved and rendered in the formatted output under "Additional
50
+ Context". Use this to carry domain-specific metadata you want the model to
51
+ see.
52
+
53
+ ## Example
54
+
55
+ ```json
56
+ [
57
+ {
58
+ "name": "founder-mode-onboarding",
59
+ "description": "Concierge onboarding videos recorded by the founder",
60
+ "target_customer": "B2B SaaS solopreneurs $0–$10k MRR",
61
+ "problem": "Activation drops 60% on day 2 of a free trial",
62
+ "solution": "Loom-style 90s personalized welcome per signup",
63
+ "revenue_model": "subscription, $79/mo add-on",
64
+ "price_point": "$79/mo",
65
+ "revenue_goal_min": "$2k MRR in 90 days",
66
+ "validation": "12 founders agreed to pilot"
67
+ }
68
+ ]
69
+ ```
70
+
71
+ ## Companion flags
72
+
73
+ - `--intuition "<text>"` — founder's qualitative gut feel about the list
74
+ - `--target-idea "<slug>"` — evaluate a single idea (slug = name in
75
+ kebab-case, e.g. `founder-mode-onboarding`)
76
+
77
+ ## See also
78
+
79
+ - [`founder-input-schema.md`](./founder-input-schema.md) — diagnostics that
80
+ `run` / `workflow` / `smoke-test` consume
81
+ - [`commands.md`](./commands.md) — endpoint + credit cost per command
82
+ - [`error-codes.md`](./error-codes.md) — exit-code recovery guide