idea-gauntlet 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.
package/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # IdeaGauntlet
2
+
3
+ Stress-test product ideas with adversarial agents, synthetic users, and court-style critique.
4
+
5
+ > Most AI tools help you generate more ideas. IdeaGauntlet helps you survive the one you already have.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/idea-gauntlet)](https://www.npmjs.com/package/idea-gauntlet)
8
+
9
+ ## Quick start
10
+
11
+ ```bash
12
+ npx idea-gauntlet quick "A synthetic focus room app for remote workers" --mock
13
+ ```
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install idea-gauntlet
19
+ ```
20
+
21
+ Or run directly:
22
+
23
+ ```bash
24
+ npx idea-gauntlet quick "Your product idea here"
25
+ ```
26
+
27
+ ## Usage Paths
28
+
29
+ | Path | Provider? | Description |
30
+ |---|---|---|
31
+ | **Standalone CLI** | API key or Ollama | `idea-gauntlet quick "idea"` |
32
+ | **Mock mode** | None needed | `idea-gauntlet quick "idea" --mock` |
33
+ | **Prompt mode** | None needed | `idea-gauntlet prompt quick "idea"` |
34
+ | **Agent-native** | None needed | `idea-gauntlet setup` |
35
+
36
+ ## CLI Commands
37
+
38
+ ### quick
39
+ Fast adversarial critique.
40
+ ```bash
41
+ idea-gauntlet quick "Your idea" [--mock] [--json] [--output report.md]
42
+ ```
43
+
44
+ ### court
45
+ Multi-role structured debate.
46
+ ```bash
47
+ idea-gauntlet court idea.md [--mock]
48
+ ```
49
+
50
+ ### users
51
+ Generate synthetic user personas.
52
+ ```bash
53
+ idea-gauntlet users idea.md --personas 6 [--mock]
54
+ ```
55
+
56
+ ### mvp
57
+ Generate a 14-day validation plan.
58
+ ```bash
59
+ idea-gauntlet mvp idea.md [--mock]
60
+ ```
61
+
62
+ ### compare
63
+ Compare multiple ideas.
64
+ ```bash
65
+ idea-gauntlet compare idea1.md idea2.md [--mock]
66
+ ```
67
+
68
+ ### prompt
69
+ Generate structured prompts (no API key needed).
70
+ ```bash
71
+ idea-gauntlet prompt quick "Your idea"
72
+ idea-gauntlet prompt court idea.md
73
+ idea-gauntlet prompt users idea.md --personas 8
74
+ ```
75
+
76
+ ### init
77
+ Scaffold a workspace.
78
+ ```bash
79
+ idea-gauntlet init [--directory path]
80
+ ```
81
+
82
+ ### setup
83
+ Generate integration files for Claude Code, Cursor, Codex.
84
+ ```bash
85
+ idea-gauntlet setup [--all] [--dry-run]
86
+ ```
87
+
88
+ ### doctor
89
+ Check environment and configuration.
90
+ ```bash
91
+ idea-gauntlet doctor
92
+ ```
93
+
94
+ ### mcp
95
+ Start MCP server for Claude Desktop integration.
96
+ ```bash
97
+ idea-gauntlet mcp
98
+ ```
99
+
100
+ ## TypeScript API
101
+
102
+ ```typescript
103
+ import { runGauntlet, MockProvider } from "idea-gauntlet";
104
+
105
+ const report = await runGauntlet({
106
+ idea: "A synthetic focus room app for remote workers",
107
+ targetUsers: ["remote workers", "students"],
108
+ mode: "quick",
109
+ provider: new MockProvider(),
110
+ });
111
+
112
+ console.log(report.markdown);
113
+ ```
114
+
115
+ ## Scoring Philosophy
116
+
117
+ Scores are diagnostic signals, not predictions.
118
+
119
+ | Dimension | What it measures |
120
+ |---|---|
121
+ | Clarity | Is the idea specific and well-defined? |
122
+ | Pain | Does the user have a real, painful problem? |
123
+ | Differentiation | Is the approach distinct? |
124
+ | Buildability | Can a small team build this? |
125
+ | Distribution | Can this reach its target users? |
126
+ | Monetization | Is there a clear revenue model? |
127
+ | Evidence | What real evidence supports the idea? |
128
+
129
+ Evidence score defaults low unless you provide real user evidence.
130
+
131
+ ## No API Key? No Problem.
132
+
133
+ - **Prompt mode** generates structured prompts for any AI
134
+ - **Mock mode** runs deterministically for demos
135
+ - **Agent-native mode** generates Claude/Cursor/Codex integration files
136
+ - **Setup** never requires an API key
137
+
138
+ ## Product Philosophy
139
+
140
+ IdeaGauntlet is:
141
+ - adversarial but useful
142
+ - skeptical but not cynical
143
+ - evidence-aware
144
+ - honest about uncertainty
145
+
146
+ IdeaGauntlet is not:
147
+ - a startup idea generator
148
+ - a replacement for real user research
149
+ - a market research oracle
150
+ - a hype machine
151
+
152
+ ## License
153
+
154
+ MIT
@@ -0,0 +1,379 @@
1
+ import {
2
+ MockProvider,
3
+ buildReport,
4
+ resolveProvider,
5
+ runCompareEngine,
6
+ runCourtEngine,
7
+ runImmuneEngine,
8
+ runMvpPlanner,
9
+ runUserLab
10
+ } from "./chunk-P4FDULQC.js";
11
+ import {
12
+ skeptic
13
+ } from "./chunk-VQHEJYTS.js";
14
+ import {
15
+ defender
16
+ } from "./chunk-FF7CULAJ.js";
17
+
18
+ // src/utils/safeWrite.ts
19
+ import { existsSync, writeFileSync } from "fs";
20
+ import { isAbsolute, resolve } from "path";
21
+ function safeWriteOutput(outputPath, content, label) {
22
+ if (!outputPath) {
23
+ console.log(content);
24
+ return { ok: true, path: "" };
25
+ }
26
+ const resolved2 = resolve(process.cwd(), outputPath);
27
+ const parts = outputPath.split(/[\\/]/);
28
+ if (!isAbsolute(outputPath) && parts.includes("..")) {
29
+ const cwd = process.cwd();
30
+ if (!resolved2.startsWith(cwd)) {
31
+ return {
32
+ ok: false,
33
+ reason: "traversal",
34
+ message: `Path traversal detected: '${outputPath}'. Use an absolute path or a path within the working directory.`
35
+ };
36
+ }
37
+ }
38
+ if (existsSync(resolved2)) {
39
+ return {
40
+ ok: false,
41
+ reason: "exists",
42
+ message: `'${outputPath}' already exists. Use --force to overwrite.`
43
+ };
44
+ }
45
+ writeFileSync(resolved2, content, "utf-8");
46
+ console.log(`${label ?? "File"} written to ${outputPath}`);
47
+ return { ok: true, path: resolved2 };
48
+ }
49
+ function safeWriteReport(reportId, content, workspaceDir) {
50
+ const baseDir = resolve(workspaceDir, ".idea-gauntlet", "reports");
51
+ const filePath = resolve(baseDir, `${reportId}.md`);
52
+ if (!filePath.startsWith(baseDir)) {
53
+ return {
54
+ ok: false,
55
+ reason: "traversal",
56
+ message: "Invalid path: report must be written inside .idea-gauntlet/reports/"
57
+ };
58
+ }
59
+ if (!existsSync(baseDir)) {
60
+ import("fs").then((fs) => fs.mkdirSync(baseDir, { recursive: true }));
61
+ }
62
+ writeFileSync(filePath, content, "utf-8");
63
+ return { ok: true, path: filePath };
64
+ }
65
+
66
+ // src/mcp/tools.ts
67
+ var reports = /* @__PURE__ */ new Map();
68
+ var provider = new MockProvider();
69
+ var resolved = resolveProvider({});
70
+ if (resolved) provider = resolved.provider;
71
+ function getReportIds() {
72
+ return Array.from(reports.keys());
73
+ }
74
+ async function handleToolCall(name, args) {
75
+ switch (name) {
76
+ case "create_prompt": {
77
+ const mode = args.mode ?? "quick";
78
+ const ideaText = args.idea ?? "";
79
+ if (!ideaText) throw new Error("idea parameter required");
80
+ let output = "";
81
+ const idea = { idea: ideaText };
82
+ switch (mode) {
83
+ case "quick": {
84
+ const sk = skeptic(idea);
85
+ const df = defender(idea);
86
+ output = [
87
+ "# IdeaGauntlet \u2014 Quick Critique Prompt",
88
+ "",
89
+ "Copy the prompts below into your AI assistant.",
90
+ "",
91
+ "---",
92
+ "",
93
+ "## Prompt 1: Skeptic",
94
+ `**System:** ${sk.system}`,
95
+ "",
96
+ sk.userMessage,
97
+ "",
98
+ "## Prompt 2: Defender",
99
+ `**System:** ${df.system}`,
100
+ "",
101
+ df.userMessage,
102
+ "",
103
+ "## Instructions for the AI",
104
+ "Respond to both prompts, then synthesize a verdict with: core insight, strongest case, weakest assumption, top failure modes, dangerous assumptions, kill tests, and next actions."
105
+ ].join("\n");
106
+ break;
107
+ }
108
+ case "court": {
109
+ const roles = [
110
+ "Prosecutor",
111
+ "Defender",
112
+ "User Advocate",
113
+ "Investor",
114
+ "Competitor",
115
+ "Judge"
116
+ ];
117
+ const roleDescriptions = [
118
+ "attacks the idea",
119
+ "argues why it could work",
120
+ "argues from the user's perspective",
121
+ "evaluates market, scale, defensibility",
122
+ "explains how the idea could be copied or crushed",
123
+ "summarizes the verdict"
124
+ ];
125
+ output = [
126
+ "# IdeaGauntlet \u2014 Court Debate Prompt",
127
+ "",
128
+ `Product idea: ${ideaText}`,
129
+ "",
130
+ "Run a structured debate with these roles:",
131
+ ...roles.map(
132
+ (r, i) => `${i + 1}. **${r}** \u2014 ${roleDescriptions[i]}`
133
+ ),
134
+ "",
135
+ "Each role speaks once, max 300 words. End with a judge verdict and unresolved questions."
136
+ ].join("\n");
137
+ break;
138
+ }
139
+ case "users": {
140
+ const count = args.personas ?? "6";
141
+ output = [
142
+ "# IdeaGauntlet \u2014 Synthetic User Prompt",
143
+ "",
144
+ `Product idea: ${ideaText}`,
145
+ "",
146
+ `Generate ${count} fictional user archetypes who would encounter this product.`,
147
+ "",
148
+ "For each persona, include:",
149
+ "- Name and archetype label",
150
+ "- Goal they are trying to accomplish",
151
+ "- Current workaround (what they do today)",
152
+ "- Trigger that would make them try this product",
153
+ "- Primary objection (why they would hesitate)",
154
+ "- Willingness to pay (none/low/medium/high)",
155
+ "- Likely reason they would churn",
156
+ "- A quote expressing their skepticism",
157
+ "- One question a founder should ask a real user like this",
158
+ "",
159
+ "**IMPORTANT:** These are fictional archetypes for hypothesis generation, not real validation."
160
+ ].join("\n");
161
+ break;
162
+ }
163
+ case "mvp": {
164
+ output = [
165
+ "# IdeaGauntlet \u2014 MVP Validation Plan Prompt",
166
+ "",
167
+ `Product idea: ${ideaText}`,
168
+ "",
169
+ "Generate an aggressive MVP validation plan with:",
170
+ "- 14-day MVP plan (max 3 things to build)",
171
+ "- Fake-door test design (what would the landing page say?)",
172
+ "- User interview script (5 questions to ask)",
173
+ "- Success metrics (what numbers justify continuing?)",
174
+ "- Kill criteria (what results mean pivot or stop?)",
175
+ "- Pivot options (adjacent directions if the core doesn't work)",
176
+ "",
177
+ "Be aggressive about reducing scope."
178
+ ].join("\n");
179
+ break;
180
+ }
181
+ default:
182
+ throw new Error(
183
+ `Unknown prompt mode: ${mode}. Use: quick, court, users, mvp`
184
+ );
185
+ }
186
+ return { type: "text", text: output };
187
+ }
188
+ case "quick_critique": {
189
+ if (!args.idea) throw new Error("idea required");
190
+ const report = await runImmuneEngine({ idea: args.idea }, provider);
191
+ report.markdown = buildReport(report);
192
+ reports.set(report.id, report);
193
+ return { type: "text", text: report.markdown };
194
+ }
195
+ case "run_court": {
196
+ if (!args.idea) throw new Error("idea required");
197
+ const report = await runCourtEngine({ idea: args.idea }, provider);
198
+ reports.set(report.id, report);
199
+ return { type: "text", text: report.markdown };
200
+ }
201
+ case "generate_users": {
202
+ if (!args.idea) throw new Error("idea required");
203
+ const report = await runUserLab(
204
+ { idea: args.idea },
205
+ provider,
206
+ args.personas ?? 6
207
+ );
208
+ reports.set(report.id, report);
209
+ return { type: "text", text: report.markdown };
210
+ }
211
+ case "plan_mvp": {
212
+ if (!args.idea) throw new Error("idea required");
213
+ const report = await runMvpPlanner({ idea: args.idea }, provider);
214
+ reports.set(report.id, report);
215
+ return { type: "text", text: report.markdown };
216
+ }
217
+ case "compare_ideas": {
218
+ if (!args.ideas || !Array.isArray(args.ideas))
219
+ throw new Error("ideas array required");
220
+ const ideas = args.ideas.map((i) => ({ idea: i }));
221
+ const report = await runCompareEngine(ideas, provider);
222
+ reports.set(report.id, report);
223
+ return { type: "text", text: report.markdown };
224
+ }
225
+ case "save_report": {
226
+ if (!args.id) throw new Error("report id required");
227
+ const report = reports.get(args.id);
228
+ if (!report) throw new Error(`Report not found: ${args.id}`);
229
+ const result = safeWriteReport(args.id, report.markdown, process.cwd());
230
+ if (!result.ok) throw new Error(result.message);
231
+ return { type: "text", text: `Report saved to ${result.path}` };
232
+ }
233
+ default:
234
+ throw new Error(`Unknown tool: ${name}`);
235
+ }
236
+ }
237
+ var toolDefinitions = [
238
+ {
239
+ name: "create_prompt",
240
+ description: "Generate a structured prompt for any mode",
241
+ inputSchema: {
242
+ type: "object",
243
+ properties: {
244
+ mode: { type: "string" },
245
+ idea: { type: "string" }
246
+ }
247
+ }
248
+ },
249
+ {
250
+ name: "quick_critique",
251
+ description: "Run quick adversarial critique",
252
+ inputSchema: {
253
+ type: "object",
254
+ properties: { idea: { type: "string" } },
255
+ required: ["idea"]
256
+ }
257
+ },
258
+ {
259
+ name: "run_court",
260
+ description: "Run court-style debate",
261
+ inputSchema: {
262
+ type: "object",
263
+ properties: { idea: { type: "string" } },
264
+ required: ["idea"]
265
+ }
266
+ },
267
+ {
268
+ name: "generate_users",
269
+ description: "Generate synthetic user personas",
270
+ inputSchema: {
271
+ type: "object",
272
+ properties: {
273
+ idea: { type: "string" },
274
+ personas: { type: "number" }
275
+ },
276
+ required: ["idea"]
277
+ }
278
+ },
279
+ {
280
+ name: "plan_mvp",
281
+ description: "Generate MVP validation plan",
282
+ inputSchema: {
283
+ type: "object",
284
+ properties: { idea: { type: "string" } },
285
+ required: ["idea"]
286
+ }
287
+ },
288
+ {
289
+ name: "compare_ideas",
290
+ description: "Compare multiple ideas",
291
+ inputSchema: {
292
+ type: "object",
293
+ properties: {
294
+ ideas: { type: "array", items: { type: "string" } }
295
+ },
296
+ required: ["ideas"]
297
+ }
298
+ },
299
+ {
300
+ name: "save_report",
301
+ description: "Save report to .idea-gauntlet/reports/",
302
+ inputSchema: {
303
+ type: "object",
304
+ properties: { id: { type: "string" } },
305
+ required: ["id"]
306
+ }
307
+ }
308
+ ];
309
+
310
+ // src/mcp/resources.ts
311
+ function listResources(reportIds) {
312
+ return {
313
+ resources: [
314
+ ...reportIds.map((id) => ({
315
+ uri: `idea-gauntlet://report/${id}`,
316
+ name: `Report ${id}`,
317
+ mimeType: "text/markdown"
318
+ })),
319
+ ...reportIds.map((id) => ({
320
+ uri: `idea-gauntlet://report/${id}/summary`,
321
+ name: `Report ${id} Summary`,
322
+ mimeType: "text/plain"
323
+ }))
324
+ ]
325
+ };
326
+ }
327
+
328
+ // src/mcp/server.ts
329
+ function respond(id, result, error) {
330
+ const msg = { jsonrpc: "2.0", id };
331
+ if (error) {
332
+ msg.error = { code: error.code ?? -32e3, message: error.message ?? "Unknown error" };
333
+ } else {
334
+ msg.result = result;
335
+ }
336
+ process.stdout.write(JSON.stringify(msg) + "\n");
337
+ }
338
+ function handleMessage(line) {
339
+ const trimmed = line.trim();
340
+ if (!trimmed) return;
341
+ let msg;
342
+ try {
343
+ msg = JSON.parse(trimmed);
344
+ } catch {
345
+ console.error("Failed to parse JSON-RPC message:", trimmed.slice(0, 200));
346
+ return;
347
+ }
348
+ if (msg.method === "tools/list") {
349
+ respond(msg.id, { tools: toolDefinitions });
350
+ } else if (msg.method === "tools/call") {
351
+ handleToolCall(msg.params.name, msg.params.arguments ?? {}).then((result) => respond(msg.id, { content: [result] })).catch(
352
+ (err) => respond(msg.id, null, { code: -32e3, message: err.message })
353
+ );
354
+ } else if (msg.method === "resources/list") {
355
+ respond(msg.id, listResources(getReportIds()));
356
+ } else {
357
+ respond(msg.id, null, { code: -32601, message: "Method not found" });
358
+ }
359
+ }
360
+ function startMcpServer() {
361
+ let buffer = "";
362
+ process.stdin.on("data", (chunk) => {
363
+ buffer += chunk.toString("utf-8");
364
+ const lines = buffer.split("\n");
365
+ buffer = lines.pop() ?? "";
366
+ for (const line of lines) {
367
+ handleMessage(line);
368
+ }
369
+ });
370
+ process.stdin.on("error", (err) => {
371
+ console.error("stdin error:", err.message);
372
+ });
373
+ process.stdin.resume();
374
+ }
375
+
376
+ export {
377
+ safeWriteOutput,
378
+ startMcpServer
379
+ };
@@ -0,0 +1,11 @@
1
+ // src/agents/defender.ts
2
+ var defender = (idea) => ({
3
+ system: "You are the Defender in IdeaGauntlet. Your job is to make the strongest honest case for the product idea. Do not exaggerate. Do not invent evidence. Identify the most compelling wedge, the likely early adopters, and the narrow version of the idea most likely to work.",
4
+ userMessage: `Product idea: ${idea.idea}
5
+
6
+ Make the strongest honest case for this idea. Focus on the real problem it solves, the users who need it most, and the version most likely to work. Return plain text, 2-3 paragraphs.`
7
+ });
8
+
9
+ export {
10
+ defender
11
+ };