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.
- package/bin/gswd-tools.cjs +228 -0
- package/commands/gswd/imagine.md +7 -1
- package/commands/gswd/start.md +507 -32
- package/dist/lib/audit.d.ts +205 -0
- package/dist/lib/audit.js +805 -0
- package/dist/lib/bootstrap.d.ts +103 -0
- package/dist/lib/bootstrap.js +563 -0
- package/dist/lib/compile.d.ts +239 -0
- package/dist/lib/compile.js +1152 -0
- package/dist/lib/config.d.ts +49 -0
- package/dist/lib/config.js +150 -0
- package/dist/lib/imagine-agents.d.ts +54 -0
- package/dist/lib/imagine-agents.js +185 -0
- package/dist/lib/imagine-gate.d.ts +47 -0
- package/dist/lib/imagine-gate.js +131 -0
- package/dist/lib/imagine-input.d.ts +46 -0
- package/dist/lib/imagine-input.js +233 -0
- package/dist/lib/imagine-synthesis.d.ts +90 -0
- package/dist/lib/imagine-synthesis.js +453 -0
- package/dist/lib/imagine.d.ts +56 -0
- package/dist/lib/imagine.js +413 -0
- package/dist/lib/intake.d.ts +27 -0
- package/dist/lib/intake.js +82 -0
- package/dist/lib/parse.d.ts +59 -0
- package/dist/lib/parse.js +171 -0
- package/dist/lib/render.d.ts +309 -0
- package/dist/lib/render.js +624 -0
- package/dist/lib/specify-agents.d.ts +120 -0
- package/dist/lib/specify-agents.js +269 -0
- package/dist/lib/specify-journeys.d.ts +124 -0
- package/dist/lib/specify-journeys.js +279 -0
- package/dist/lib/specify-nfr.d.ts +45 -0
- package/dist/lib/specify-nfr.js +159 -0
- package/dist/lib/specify-roles.d.ts +46 -0
- package/dist/lib/specify-roles.js +88 -0
- package/dist/lib/specify.d.ts +70 -0
- package/dist/lib/specify.js +676 -0
- package/dist/lib/state.d.ts +140 -0
- package/dist/lib/state.js +340 -0
- package/dist/tests/audit.test.d.ts +4 -0
- package/dist/tests/audit.test.js +1579 -0
- package/dist/tests/bootstrap.test.d.ts +5 -0
- package/dist/tests/bootstrap.test.js +611 -0
- package/dist/tests/compile.test.d.ts +4 -0
- package/dist/tests/compile.test.js +862 -0
- package/dist/tests/config.test.d.ts +4 -0
- package/dist/tests/config.test.js +191 -0
- package/dist/tests/imagine-agents.test.d.ts +6 -0
- package/dist/tests/imagine-agents.test.js +179 -0
- package/dist/tests/imagine-gate.test.d.ts +6 -0
- package/dist/tests/imagine-gate.test.js +264 -0
- package/dist/tests/imagine-input.test.d.ts +6 -0
- package/dist/tests/imagine-input.test.js +283 -0
- package/dist/tests/imagine-synthesis.test.d.ts +7 -0
- package/dist/tests/imagine-synthesis.test.js +380 -0
- package/dist/tests/imagine.test.d.ts +8 -0
- package/dist/tests/imagine.test.js +406 -0
- package/dist/tests/parse.test.d.ts +4 -0
- package/dist/tests/parse.test.js +285 -0
- package/dist/tests/render.test.d.ts +4 -0
- package/dist/tests/render.test.js +236 -0
- package/dist/tests/specify-agents.test.d.ts +4 -0
- package/dist/tests/specify-agents.test.js +352 -0
- package/dist/tests/specify-journeys.test.d.ts +5 -0
- package/dist/tests/specify-journeys.test.js +440 -0
- package/dist/tests/specify-nfr.test.d.ts +4 -0
- package/dist/tests/specify-nfr.test.js +205 -0
- package/dist/tests/specify-roles.test.d.ts +4 -0
- package/dist/tests/specify-roles.test.js +136 -0
- package/dist/tests/specify.test.d.ts +9 -0
- package/dist/tests/specify.test.js +544 -0
- package/dist/tests/state.test.d.ts +4 -0
- package/dist/tests/state.test.js +316 -0
- package/lib/bootstrap.ts +37 -11
- package/lib/compile.ts +426 -4
- package/lib/imagine-agents.ts +53 -7
- package/lib/imagine-synthesis.ts +170 -6
- package/lib/imagine.ts +59 -5
- package/lib/intake.ts +60 -0
- package/lib/parse.ts +2 -1
- package/lib/render.ts +566 -5
- package/lib/specify-agents.ts +25 -3
- package/lib/state.ts +115 -0
- package/package.json +3 -2
- 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
|
+
};
|