gswd 1.0.1 → 1.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.
Files changed (85) hide show
  1. package/bin/gswd-tools.cjs +228 -0
  2. package/commands/gswd/imagine.md +7 -1
  3. package/commands/gswd/start.md +507 -32
  4. package/dist/lib/audit.d.ts +205 -0
  5. package/dist/lib/audit.js +805 -0
  6. package/dist/lib/bootstrap.d.ts +103 -0
  7. package/dist/lib/bootstrap.js +563 -0
  8. package/dist/lib/compile.d.ts +239 -0
  9. package/dist/lib/compile.js +1152 -0
  10. package/dist/lib/config.d.ts +49 -0
  11. package/dist/lib/config.js +150 -0
  12. package/dist/lib/imagine-agents.d.ts +54 -0
  13. package/dist/lib/imagine-agents.js +185 -0
  14. package/dist/lib/imagine-gate.d.ts +47 -0
  15. package/dist/lib/imagine-gate.js +131 -0
  16. package/dist/lib/imagine-input.d.ts +46 -0
  17. package/dist/lib/imagine-input.js +233 -0
  18. package/dist/lib/imagine-synthesis.d.ts +90 -0
  19. package/dist/lib/imagine-synthesis.js +453 -0
  20. package/dist/lib/imagine.d.ts +56 -0
  21. package/dist/lib/imagine.js +413 -0
  22. package/dist/lib/intake.d.ts +27 -0
  23. package/dist/lib/intake.js +82 -0
  24. package/dist/lib/parse.d.ts +59 -0
  25. package/dist/lib/parse.js +171 -0
  26. package/dist/lib/render.d.ts +309 -0
  27. package/dist/lib/render.js +624 -0
  28. package/dist/lib/specify-agents.d.ts +120 -0
  29. package/dist/lib/specify-agents.js +269 -0
  30. package/dist/lib/specify-journeys.d.ts +124 -0
  31. package/dist/lib/specify-journeys.js +279 -0
  32. package/dist/lib/specify-nfr.d.ts +45 -0
  33. package/dist/lib/specify-nfr.js +159 -0
  34. package/dist/lib/specify-roles.d.ts +46 -0
  35. package/dist/lib/specify-roles.js +88 -0
  36. package/dist/lib/specify.d.ts +70 -0
  37. package/dist/lib/specify.js +676 -0
  38. package/dist/lib/state.d.ts +140 -0
  39. package/dist/lib/state.js +340 -0
  40. package/dist/tests/audit.test.d.ts +4 -0
  41. package/dist/tests/audit.test.js +1579 -0
  42. package/dist/tests/bootstrap.test.d.ts +5 -0
  43. package/dist/tests/bootstrap.test.js +611 -0
  44. package/dist/tests/compile.test.d.ts +4 -0
  45. package/dist/tests/compile.test.js +862 -0
  46. package/dist/tests/config.test.d.ts +4 -0
  47. package/dist/tests/config.test.js +191 -0
  48. package/dist/tests/imagine-agents.test.d.ts +6 -0
  49. package/dist/tests/imagine-agents.test.js +179 -0
  50. package/dist/tests/imagine-gate.test.d.ts +6 -0
  51. package/dist/tests/imagine-gate.test.js +264 -0
  52. package/dist/tests/imagine-input.test.d.ts +6 -0
  53. package/dist/tests/imagine-input.test.js +283 -0
  54. package/dist/tests/imagine-synthesis.test.d.ts +7 -0
  55. package/dist/tests/imagine-synthesis.test.js +380 -0
  56. package/dist/tests/imagine.test.d.ts +8 -0
  57. package/dist/tests/imagine.test.js +406 -0
  58. package/dist/tests/parse.test.d.ts +4 -0
  59. package/dist/tests/parse.test.js +285 -0
  60. package/dist/tests/render.test.d.ts +4 -0
  61. package/dist/tests/render.test.js +236 -0
  62. package/dist/tests/specify-agents.test.d.ts +4 -0
  63. package/dist/tests/specify-agents.test.js +352 -0
  64. package/dist/tests/specify-journeys.test.d.ts +5 -0
  65. package/dist/tests/specify-journeys.test.js +440 -0
  66. package/dist/tests/specify-nfr.test.d.ts +4 -0
  67. package/dist/tests/specify-nfr.test.js +205 -0
  68. package/dist/tests/specify-roles.test.d.ts +4 -0
  69. package/dist/tests/specify-roles.test.js +136 -0
  70. package/dist/tests/specify.test.d.ts +9 -0
  71. package/dist/tests/specify.test.js +544 -0
  72. package/dist/tests/state.test.d.ts +4 -0
  73. package/dist/tests/state.test.js +316 -0
  74. package/lib/bootstrap.ts +37 -11
  75. package/lib/compile.ts +426 -4
  76. package/lib/imagine-agents.ts +53 -7
  77. package/lib/imagine-synthesis.ts +170 -6
  78. package/lib/imagine.ts +59 -5
  79. package/lib/intake.ts +60 -0
  80. package/lib/parse.ts +2 -1
  81. package/lib/render.ts +566 -5
  82. package/lib/specify-agents.ts +25 -3
  83. package/lib/state.ts +115 -0
  84. package/package.json +3 -2
  85. package/templates/gswd/DECISIONS.template.md +3 -0
@@ -0,0 +1,233 @@
1
+ "use strict";
2
+ /**
3
+ * GSWD Imagine Input Module — Input collection with file parsing and intake building
4
+ *
5
+ * Two input paths converge to a single StarterBrief interface:
6
+ * 1. parseIdeaFile: Parse @idea.md into a starter brief
7
+ * 2. buildFromIntake: Build from 3-question intake answers
8
+ *
9
+ * Schema: GSWD_SPEC.md Section 8.2, Steps 1-2
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.parseIdeaFile = parseIdeaFile;
46
+ exports.buildFromIntake = buildFromIntake;
47
+ exports.validateBrief = validateBrief;
48
+ const fs = __importStar(require("node:fs"));
49
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
50
+ /**
51
+ * Find the first sentence containing any of the given keywords.
52
+ * Returns the full sentence or null if no match.
53
+ */
54
+ function findSentenceWithKeywords(content, keywords) {
55
+ // Split into sentences (rough: split on . ! ? followed by space or end)
56
+ const sentences = content.split(/(?<=[.!?])\s+/);
57
+ const lowerKeywords = keywords.map(k => k.toLowerCase());
58
+ for (const sentence of sentences) {
59
+ const lower = sentence.toLowerCase();
60
+ for (const keyword of lowerKeywords) {
61
+ if (lower.includes(keyword)) {
62
+ return sentence.trim();
63
+ }
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+ /**
69
+ * Extract the first meaningful paragraph (>20 chars, not a heading).
70
+ */
71
+ function extractFirstParagraph(content) {
72
+ const lines = content.split('\n');
73
+ let paragraph = '';
74
+ for (const line of lines) {
75
+ const trimmed = line.trim();
76
+ // Skip headings and empty lines
77
+ if (trimmed.startsWith('#') || trimmed === '') {
78
+ if (paragraph.length > 20)
79
+ return paragraph.trim();
80
+ paragraph = '';
81
+ continue;
82
+ }
83
+ paragraph += (paragraph ? ' ' : '') + trimmed;
84
+ }
85
+ return paragraph.length > 20 ? paragraph.trim() : '';
86
+ }
87
+ /**
88
+ * Extract raw themes from content: headings, bullets, bold text.
89
+ * Deduplicates and limits to 20 items.
90
+ */
91
+ function extractThemes(content) {
92
+ const themes = new Set();
93
+ const lines = content.split('\n');
94
+ for (const line of lines) {
95
+ const trimmed = line.trim();
96
+ // Headings (strip # prefix)
97
+ if (trimmed.startsWith('#')) {
98
+ const heading = trimmed.replace(/^#+\s*/, '').trim();
99
+ if (heading.length > 2)
100
+ themes.add(heading);
101
+ }
102
+ // Bullet points (strip - or * prefix)
103
+ if (/^[-*]\s+/.test(trimmed)) {
104
+ const bullet = trimmed.replace(/^[-*]\s+/, '').trim();
105
+ if (bullet.length > 2)
106
+ themes.add(bullet);
107
+ }
108
+ // Numbered items
109
+ if (/^\d+[.)]\s+/.test(trimmed)) {
110
+ const item = trimmed.replace(/^\d+[.)]\s+/, '').trim();
111
+ if (item.length > 2)
112
+ themes.add(item);
113
+ }
114
+ }
115
+ // Also extract bold text (**text**)
116
+ const boldRegex = /\*\*([^*]+)\*\*/g;
117
+ let match;
118
+ while ((match = boldRegex.exec(content)) !== null) {
119
+ const bold = match[1].trim();
120
+ if (bold.length > 2)
121
+ themes.add(bold);
122
+ }
123
+ return Array.from(themes).slice(0, 20);
124
+ }
125
+ /**
126
+ * Extract themes from intake answers by splitting on delimiters.
127
+ */
128
+ function extractIntakeThemes(combined) {
129
+ const themes = new Set();
130
+ // Split on commas, semicolons, and " and " conjunctions
131
+ const parts = combined.split(/[,;]|\s+and\s+/i);
132
+ for (const part of parts) {
133
+ const trimmed = part.trim();
134
+ // Filter out very short fragments and common filler words
135
+ if (trimmed.length > 3) {
136
+ themes.add(trimmed);
137
+ }
138
+ }
139
+ return Array.from(themes).slice(0, 20);
140
+ }
141
+ // ─── Public API ──────────────────────────────────────────────────────────────
142
+ /**
143
+ * Parse an @idea.md file into a StarterBrief.
144
+ *
145
+ * Extracts vision, target user, timing, and themes from freeform markdown.
146
+ * Falls back to first 200 chars if specific fields can't be extracted.
147
+ * Throws on empty or non-existent files.
148
+ */
149
+ function parseIdeaFile(filePath) {
150
+ // Read file
151
+ let content;
152
+ try {
153
+ content = fs.readFileSync(filePath, 'utf-8');
154
+ }
155
+ catch (err) {
156
+ const message = err instanceof Error ? err.message : String(err);
157
+ throw new Error(`Cannot read idea file "${filePath}": ${message}`);
158
+ }
159
+ // Check for empty file
160
+ if (content.trim().length < 10) {
161
+ throw new Error(`Idea file "${filePath}" is too short (< 10 chars). Provide a more detailed description.`);
162
+ }
163
+ const fallback = content.trim().slice(0, 200);
164
+ // Extract vision: first heading content or first paragraph
165
+ const headingMatch = content.match(/^#+\s+(.+)$/m);
166
+ let vision = headingMatch ? headingMatch[1].trim() : '';
167
+ if (!vision || vision.length <= 20) {
168
+ const para = extractFirstParagraph(content);
169
+ vision = para || fallback;
170
+ }
171
+ // Extract target user
172
+ const userKeywords = ['for', 'target', 'customer', 'audience', 'users', 'people who', 'designed for', 'built for'];
173
+ let target_user = findSentenceWithKeywords(content, userKeywords) || '';
174
+ if (!target_user) {
175
+ target_user = fallback;
176
+ }
177
+ // Extract why now
178
+ const whyNowKeywords = ['because', 'opportunity', 'problem', 'pain', 'frustration', 'gap', 'trend', 'growing', 'increasing'];
179
+ let why_now = findSentenceWithKeywords(content, whyNowKeywords) || '';
180
+ if (!why_now) {
181
+ why_now = fallback;
182
+ }
183
+ // Extract themes
184
+ const raw_themes = extractThemes(content);
185
+ return {
186
+ vision,
187
+ target_user,
188
+ why_now,
189
+ raw_themes,
190
+ source: 'file',
191
+ };
192
+ }
193
+ /**
194
+ * Build a StarterBrief from 3-question intake answers.
195
+ *
196
+ * Maps: vision -> vision, user -> target_user, whyNow -> why_now.
197
+ * Extracts themes by splitting answers on delimiters.
198
+ */
199
+ function buildFromIntake(answers) {
200
+ const combined = `${answers.vision}, ${answers.user}, ${answers.whyNow}`;
201
+ const raw_themes = extractIntakeThemes(combined);
202
+ return {
203
+ vision: answers.vision.trim(),
204
+ target_user: answers.user.trim(),
205
+ why_now: answers.whyNow.trim(),
206
+ raw_themes,
207
+ source: 'intake',
208
+ };
209
+ }
210
+ /**
211
+ * Validate that a StarterBrief has sufficient signal for research agents.
212
+ *
213
+ * Checks: vision > 10 chars, target_user > 5 chars, why_now > 5 chars, raw_themes >= 1.
214
+ */
215
+ function validateBrief(brief) {
216
+ const missing = [];
217
+ if (!brief.vision || brief.vision.length <= 10) {
218
+ missing.push('vision');
219
+ }
220
+ if (!brief.target_user || brief.target_user.length <= 5) {
221
+ missing.push('target_user');
222
+ }
223
+ if (!brief.why_now || brief.why_now.length <= 5) {
224
+ missing.push('why_now');
225
+ }
226
+ if (!brief.raw_themes || brief.raw_themes.length < 1) {
227
+ missing.push('raw_themes');
228
+ }
229
+ return {
230
+ valid: missing.length === 0,
231
+ missing,
232
+ };
233
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * GSWD Imagine Synthesis Module — Direction synthesis and auto-mode scoring
3
+ *
4
+ * Combines research from 5 agents into 3 direction options.
5
+ * Provides auto-mode scoring (pain x willingness-to-pay x reachability)
6
+ * for unattended decision-making per GSWD_SPEC Section 8.2 Step 4 and Section 10.
7
+ *
8
+ * Schema: GSWD_SPEC.md Section 8.2 Steps 4-5, Section 10
9
+ */
10
+ import type { AgentResult } from './imagine-agents.js';
11
+ import type { StarterBrief } from './imagine-input.js';
12
+ export interface Direction {
13
+ label: string;
14
+ icp_summary: string;
15
+ problem_framing: string;
16
+ wedge: string;
17
+ differentiator: string;
18
+ risks: string[];
19
+ rationale?: string[];
20
+ }
21
+ /**
22
+ * Research summary section for post-agent display.
23
+ * Produced by buildResearchSummary(), consumed by renderResearchSummary().
24
+ */
25
+ export interface ResearchSummarySection {
26
+ agentName: string;
27
+ displayName: string;
28
+ takeaways: string[];
29
+ bridge: string;
30
+ }
31
+ /**
32
+ * User-friendly display names for imagine agents.
33
+ */
34
+ export declare const AGENT_DISPLAY_NAMES: Record<string, string>;
35
+ export interface AutoDecision {
36
+ type: string;
37
+ chosen: string;
38
+ rationale: string;
39
+ score?: number;
40
+ recorded_at: string;
41
+ }
42
+ export interface SynthesisResult {
43
+ proposed: Direction;
44
+ alternatives: Direction[];
45
+ agent_warnings: string[];
46
+ raw_agent_outputs: Record<string, string>;
47
+ }
48
+ export interface ScoreEntry {
49
+ label: string;
50
+ score: number;
51
+ rationale: string;
52
+ }
53
+ export interface ScoreResult {
54
+ index: number;
55
+ scores: ScoreEntry[];
56
+ }
57
+ /**
58
+ * Synthesize 5 agent results into 3 direction options.
59
+ *
60
+ * Handles missing agent data gracefully — failed agents produce degraded
61
+ * output with warnings, not crashes.
62
+ */
63
+ export declare function synthesizeDirections(agentResults: AgentResult[]): SynthesisResult;
64
+ /**
65
+ * Score directions using pain x willingness-to-pay x reachability heuristic.
66
+ *
67
+ * Uses keyword counting as a proxy since these are text-derived signals.
68
+ * Formula: pain * 0.4 + wtp * 0.3 + reachability * 0.3
69
+ *
70
+ * Per GSWD_SPEC: "choose ICP with highest pain x willingness-to-pay x reachability score"
71
+ */
72
+ export declare function scoreIcpOptions(directions: Direction[]): ScoreResult;
73
+ /**
74
+ * Build structured research summary from agent results.
75
+ * Each section: agent display name, 3-5 takeaway bullets, bridging paragraph.
76
+ * Product-contextualized using brief.vision and brief.target_user.
77
+ */
78
+ export declare function buildResearchSummary(agentResults: AgentResult[], brief: StarterBrief): ResearchSummarySection[];
79
+ /**
80
+ * Auto-select the best direction and generate decision records.
81
+ *
82
+ * Per GSWD_SPEC Section 8.2 Auto behavior:
83
+ * - Choose ICP with highest pain x WTP x reachability score
84
+ * - Choose wedge with smallest scope that still hits an "aha"
85
+ * - Record decisions as Auto-chosen with rationale
86
+ */
87
+ export declare function autoSelectDirection(synthesis: SynthesisResult): {
88
+ selected: Direction;
89
+ decisions: AutoDecision[];
90
+ };