hardness 1.0.0 → 1.1.2
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/AGENTS.md +11 -0
- package/CHANGELOG.md +36 -0
- package/README.md +62 -15
- package/node_modules/@hardness/analyzers/package.json +1 -1
- package/node_modules/@hardness/core/dist/common/paths.js +2 -2
- package/node_modules/@hardness/core/dist/common/paths.js.map +1 -1
- package/node_modules/@hardness/core/package.json +1 -1
- package/node_modules/@hardness/prompts/package.json +1 -1
- package/package.json +1 -1
- package/packages/analyzers/package.json +1 -1
- package/packages/cli/dist/commands/discover.js +47 -6
- package/packages/cli/dist/commands/discover.js.map +1 -1
- package/packages/cli/dist/commands/plan.js +39 -6
- package/packages/cli/dist/commands/plan.js.map +1 -1
- package/packages/cli/dist/commands/spec.js +34 -6
- package/packages/cli/dist/commands/spec.js.map +1 -1
- package/packages/cli/dist/dispatcher.d.ts +2 -0
- package/packages/cli/dist/dispatcher.js +63 -62
- package/packages/cli/dist/dispatcher.js.map +1 -1
- package/packages/cli/dist/generators/prd-generator.d.ts +14 -0
- package/packages/cli/dist/generators/prd-generator.js +164 -0
- package/packages/cli/dist/generators/prd-generator.js.map +1 -0
- package/packages/cli/dist/generators/spec-generator.d.ts +35 -0
- package/packages/cli/dist/generators/spec-generator.js +245 -0
- package/packages/cli/dist/generators/spec-generator.js.map +1 -0
- package/packages/cli/dist/generators/sprint-generator.d.ts +51 -0
- package/packages/cli/dist/generators/sprint-generator.js +162 -0
- package/packages/cli/dist/generators/sprint-generator.js.map +1 -0
- package/packages/cli/dist/index.js +1 -1
- package/packages/cli/dist/interview/evaluator-prompt.d.ts +9 -0
- package/packages/cli/dist/interview/evaluator-prompt.js +192 -0
- package/packages/cli/dist/interview/evaluator-prompt.js.map +1 -0
- package/packages/cli/dist/interview/evaluator.d.ts +46 -0
- package/packages/cli/dist/interview/evaluator.js +142 -0
- package/packages/cli/dist/interview/evaluator.js.map +1 -0
- package/packages/cli/dist/interview/questions.d.ts +29 -0
- package/packages/cli/dist/interview/questions.js +642 -0
- package/packages/cli/dist/interview/questions.js.map +1 -0
- package/packages/cli/dist/interview/runner.d.ts +14 -0
- package/packages/cli/dist/interview/runner.js +327 -0
- package/packages/cli/dist/interview/runner.js.map +1 -0
- package/packages/cli/dist/interview/suggestions.d.ts +6 -0
- package/packages/cli/dist/interview/suggestions.js +230 -0
- package/packages/cli/dist/interview/suggestions.js.map +1 -0
- package/packages/cli/dist/interview/types.d.ts +46 -0
- package/packages/cli/dist/interview/types.js +50 -0
- package/packages/cli/dist/interview/types.js.map +1 -0
- package/packages/cli/package.json +1 -1
- package/packages/core/dist/common/paths.js +2 -2
- package/packages/core/dist/common/paths.js.map +1 -1
- package/packages/core/package.json +1 -1
- package/packages/prompts/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { InterviewState } from '../interview/types.js';
|
|
2
|
+
export interface PrdGeneratorOptions {
|
|
3
|
+
/** Absolute path to write PRD.md. Defaults to cwd/PRD.md. */
|
|
4
|
+
outputPath?: string;
|
|
5
|
+
/** ISO date string to stamp in the header. Defaults to today. */
|
|
6
|
+
date?: string;
|
|
7
|
+
/** Override templates dir (for testing). */
|
|
8
|
+
templatesDir?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Renders PRD.md from an InterviewState and writes it to disk.
|
|
12
|
+
* Returns the rendered markdown string.
|
|
13
|
+
*/
|
|
14
|
+
export declare function generatePrd(state: InterviewState, options?: PrdGeneratorOptions): string;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Helper: resolve the templates/ directory relative to this package
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
function findTemplatesDir() {
|
|
7
|
+
// When running from dist/, go up to the package root then to templates/
|
|
8
|
+
// In source (tests): packages/cli/src/generators/ → up 3 → project root
|
|
9
|
+
// In dist: packages/cli/dist/generators/ → up 3 → project root
|
|
10
|
+
const candidates = [];
|
|
11
|
+
// Walk up from __filename looking for templates/PRD-TEMPLATE.md
|
|
12
|
+
let dir = path.dirname(new URL(import.meta.url).pathname);
|
|
13
|
+
// On Windows, pathname starts with /D:/...; normalize
|
|
14
|
+
if (process.platform === 'win32' && dir.startsWith('/')) {
|
|
15
|
+
dir = dir.slice(1);
|
|
16
|
+
}
|
|
17
|
+
for (let i = 0; i < 6; i++) {
|
|
18
|
+
const candidate = path.join(dir, 'templates', 'PRD-TEMPLATE.md');
|
|
19
|
+
if (fs.existsSync(candidate)) {
|
|
20
|
+
candidates.push(path.join(dir, 'templates'));
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
const parent = path.dirname(dir);
|
|
24
|
+
if (parent === dir)
|
|
25
|
+
break;
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
if (candidates.length > 0)
|
|
29
|
+
return candidates[0];
|
|
30
|
+
// Fallback: use HARDNESS_ROOT if set, then cwd
|
|
31
|
+
const envRoot = process.env.HARDNESS_ROOT ?? process.cwd();
|
|
32
|
+
return path.join(envRoot, 'templates');
|
|
33
|
+
}
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Technical level label
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
const TECH_LEVEL_LABELS = {
|
|
38
|
+
'1': 'Non-technical',
|
|
39
|
+
'2': 'Semi-technical',
|
|
40
|
+
'3': 'Technical',
|
|
41
|
+
'Non-technical': 'Non-technical',
|
|
42
|
+
'Semi-technical': 'Semi-technical',
|
|
43
|
+
'Technical': 'Technical',
|
|
44
|
+
};
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Priority label
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
const PRIORITY_LABELS = {
|
|
49
|
+
'1': 'must',
|
|
50
|
+
'2': 'should',
|
|
51
|
+
'3': 'nice-to-have',
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Renders PRD.md from an InterviewState and writes it to disk.
|
|
55
|
+
* Returns the rendered markdown string.
|
|
56
|
+
*/
|
|
57
|
+
export function generatePrd(state, options = {}) {
|
|
58
|
+
const date = options.date ?? new Date().toISOString().slice(0, 10);
|
|
59
|
+
const outputPath = options.outputPath ?? path.join(process.cwd(), 'PRD.md');
|
|
60
|
+
// Read template (for structure validation; we render our own content)
|
|
61
|
+
const templatesDir = options.templatesDir ?? findTemplatesDir();
|
|
62
|
+
const templatePath = path.join(templatesDir, 'PRD-TEMPLATE.md');
|
|
63
|
+
if (!fs.existsSync(templatePath)) {
|
|
64
|
+
throw new Error(`PRD-TEMPLATE.md not found at: ${templatePath}`);
|
|
65
|
+
}
|
|
66
|
+
const projectName = state.answers['q1.1'] ?? 'Unnamed Project';
|
|
67
|
+
const oneLiner = state.answers['q1.2'] ?? '';
|
|
68
|
+
const problem = state.answers['q1.3'] ?? '';
|
|
69
|
+
const primaryPersona = state.answers['q2.1'] ?? 'user';
|
|
70
|
+
const secondaryPersonas = state.answers['q2.2'] ?? '';
|
|
71
|
+
const techLevelRaw = state.answers['q2.3'] ?? '2';
|
|
72
|
+
const techLevel = TECH_LEVEL_LABELS[techLevelRaw.split(/[\s—]/)[0].trim()] ?? techLevelRaw;
|
|
73
|
+
const features = state.answers['q3.1'];
|
|
74
|
+
const priorityRaw = state.answers['q3.2'] ?? '1';
|
|
75
|
+
const priorityKey = priorityRaw.split(/[\s—]/)[0].trim();
|
|
76
|
+
const priority = PRIORITY_LABELS[priorityKey] ?? 'must';
|
|
77
|
+
const acceptance = state.answers['q3.3'] ?? '';
|
|
78
|
+
const stack = state.detectedStack.label;
|
|
79
|
+
const database = state.answers['q4.3'] ?? 'N/A';
|
|
80
|
+
const auth = state.answers['q4.4'] ?? 'N/A';
|
|
81
|
+
const scale = state.answers['q4.5'] ?? 'N/A';
|
|
82
|
+
const deploy = state.answers['q4.6'] ?? 'N/A';
|
|
83
|
+
const performance = state.answers['q4.7'] ?? 'N/A';
|
|
84
|
+
const observability = state.answers['q4.8'] ?? 'N/A';
|
|
85
|
+
const outOfScope = state.answers['q5.1'];
|
|
86
|
+
const assumptions = state.answers['q5.2'] ?? '';
|
|
87
|
+
const risks = state.answers['q5.3'] ?? '';
|
|
88
|
+
// --- Sections ---
|
|
89
|
+
const problemText = Array.isArray(problem) ? problem.join('\n') : problem;
|
|
90
|
+
// Functional requirements
|
|
91
|
+
const featureList = Array.isArray(features) ? features : [features];
|
|
92
|
+
const frLines = featureList
|
|
93
|
+
.filter(Boolean)
|
|
94
|
+
.map((f, idx) => `- **FR-${String(idx + 1).padStart(3, '0')}** (${priority}): ${f}`)
|
|
95
|
+
.join('\n');
|
|
96
|
+
const acceptanceLine = acceptance
|
|
97
|
+
? ` - Acceptance: ${acceptance}`
|
|
98
|
+
: '';
|
|
99
|
+
// Out of scope
|
|
100
|
+
const scopeList = Array.isArray(outOfScope) ? outOfScope : [outOfScope];
|
|
101
|
+
const scopeLines = scopeList
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
.map((s) => `- ${s}`)
|
|
104
|
+
.join('\n');
|
|
105
|
+
// Personas table
|
|
106
|
+
const personaRows = [
|
|
107
|
+
`| ${primaryPersona} | ${techLevel} | Core product needs |`,
|
|
108
|
+
];
|
|
109
|
+
if (secondaryPersonas) {
|
|
110
|
+
secondaryPersonas.split(',').forEach((p) => {
|
|
111
|
+
const name = p.trim();
|
|
112
|
+
if (name)
|
|
113
|
+
personaRows.push(`| ${name} | Technical | Support / maintenance |`);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const md = `# PRD — ${projectName}
|
|
117
|
+
|
|
118
|
+
> Generated by \`hardness discover\` on ${date}.
|
|
119
|
+
> This document is the input for \`hardness spec\`.
|
|
120
|
+
|
|
121
|
+
## 1. Overview
|
|
122
|
+
|
|
123
|
+
${oneLiner}
|
|
124
|
+
|
|
125
|
+
${problemText}
|
|
126
|
+
|
|
127
|
+
## 2. Personas
|
|
128
|
+
|
|
129
|
+
| Persona | Description | Needs |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
${personaRows.join('\n')}
|
|
132
|
+
|
|
133
|
+
## 3. Functional Requirements
|
|
134
|
+
|
|
135
|
+
${frLines}
|
|
136
|
+
${acceptanceLine ? acceptanceLine : ''}
|
|
137
|
+
|
|
138
|
+
## 4. Non-Functional Requirements
|
|
139
|
+
|
|
140
|
+
- **NFR-001**: Stack — ${stack}
|
|
141
|
+
- **NFR-002**: Database — ${database}
|
|
142
|
+
- **NFR-003**: Authentication — ${auth}
|
|
143
|
+
- **NFR-004**: Scale — ${scale}
|
|
144
|
+
- **NFR-005**: Deployment — ${deploy}
|
|
145
|
+
- **NFR-006**: Performance — ${performance}
|
|
146
|
+
- **NFR-007**: Observability — ${observability}
|
|
147
|
+
|
|
148
|
+
## 5. Out of Scope (v1.0)
|
|
149
|
+
|
|
150
|
+
${scopeLines || '- (none specified)'}
|
|
151
|
+
|
|
152
|
+
## 6. Product Acceptance Criteria
|
|
153
|
+
|
|
154
|
+
- [ ] ${acceptance || 'All core features work end-to-end without errors and are covered by tests.'}
|
|
155
|
+
|
|
156
|
+
## 7. Assumptions and Risks
|
|
157
|
+
|
|
158
|
+
- **Assumptions**: ${assumptions || 'Standard runtime environment.'}
|
|
159
|
+
- **Risks**: ${risks || 'Scope may grow — strict sprint discipline required.'} — Mitigate with clear acceptance criteria per feature.
|
|
160
|
+
`;
|
|
161
|
+
fs.writeFileSync(outputPath, md, 'utf-8');
|
|
162
|
+
return md;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=prd-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prd-generator.js","sourceRoot":"","sources":["../../src/generators/prd-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface ParsedPrd {
|
|
2
|
+
projectName: string;
|
|
3
|
+
overview: string;
|
|
4
|
+
personas: string[];
|
|
5
|
+
functionalReqs: Array<{
|
|
6
|
+
id: string;
|
|
7
|
+
priority: string;
|
|
8
|
+
description: string;
|
|
9
|
+
}>;
|
|
10
|
+
nfrStack: string;
|
|
11
|
+
nfrDatabase: string;
|
|
12
|
+
nfrAuth: string;
|
|
13
|
+
nfrScale: string;
|
|
14
|
+
nfrDeploy: string;
|
|
15
|
+
nfrPerf: string;
|
|
16
|
+
nfrObs: string;
|
|
17
|
+
outOfScope: string[];
|
|
18
|
+
acceptanceCriteria: string[];
|
|
19
|
+
assumptions: string;
|
|
20
|
+
risks: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function parsePrd(content: string): ParsedPrd;
|
|
23
|
+
export interface SpecGeneratorOptions {
|
|
24
|
+
/** Absolute path to write SPEC.md. Defaults to cwd/SPEC.md. */
|
|
25
|
+
outputPath?: string;
|
|
26
|
+
/** ISO date string. Defaults to today. */
|
|
27
|
+
date?: string;
|
|
28
|
+
/** Override templates dir (for testing). */
|
|
29
|
+
templatesDir?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Reads PRD.md content, parses it, and renders SPEC.md.
|
|
33
|
+
* Returns the rendered markdown string.
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateSpec(prdContent: string, options?: SpecGeneratorOptions): string;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Template resolution (same pattern as prd-generator)
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
function findTemplatesDir() {
|
|
7
|
+
let dir = path.dirname(new URL(import.meta.url).pathname);
|
|
8
|
+
if (process.platform === 'win32' && dir.startsWith('/'))
|
|
9
|
+
dir = dir.slice(1);
|
|
10
|
+
for (let i = 0; i < 6; i++) {
|
|
11
|
+
const candidate = path.join(dir, 'templates', 'SPEC-TEMPLATE.md');
|
|
12
|
+
if (fs.existsSync(candidate))
|
|
13
|
+
return path.join(dir, 'templates');
|
|
14
|
+
const parent = path.dirname(dir);
|
|
15
|
+
if (parent === dir)
|
|
16
|
+
break;
|
|
17
|
+
dir = parent;
|
|
18
|
+
}
|
|
19
|
+
const envRoot = process.env.HARDNESS_ROOT ?? process.cwd();
|
|
20
|
+
return path.join(envRoot, 'templates');
|
|
21
|
+
}
|
|
22
|
+
export function parsePrd(content) {
|
|
23
|
+
const lines = content.split('\n');
|
|
24
|
+
// Project name from h1
|
|
25
|
+
const h1Line = lines.find((l) => l.startsWith('# PRD —'));
|
|
26
|
+
const projectName = h1Line ? h1Line.replace('# PRD —', '').trim() : 'Unknown Project';
|
|
27
|
+
// Helper: extract a section's content (between two ## headers)
|
|
28
|
+
function extractSection(header) {
|
|
29
|
+
const startIdx = lines.findIndex((l) => l.startsWith(`## ${header}`));
|
|
30
|
+
if (startIdx === -1)
|
|
31
|
+
return [];
|
|
32
|
+
const result = [];
|
|
33
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
34
|
+
if (lines[i].startsWith('## '))
|
|
35
|
+
break;
|
|
36
|
+
result.push(lines[i]);
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
// Overview
|
|
41
|
+
const overviewLines = extractSection('1. Overview').filter((l) => l.trim());
|
|
42
|
+
const overview = overviewLines.join('\n').trim();
|
|
43
|
+
// Personas (extract from table rows, skip header and separator)
|
|
44
|
+
const personaLines = extractSection('2. Personas')
|
|
45
|
+
.filter((l) => l.startsWith('|') && !l.includes('---|'))
|
|
46
|
+
.slice(1); // skip column header
|
|
47
|
+
const personas = personaLines
|
|
48
|
+
.map((l) => l.split('|')[1]?.trim())
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
// Functional Requirements
|
|
51
|
+
const frLines = extractSection('3. Functional Requirements').filter((l) => l.startsWith('- **FR-'));
|
|
52
|
+
const functionalReqs = frLines.map((l) => {
|
|
53
|
+
const match = l.match(/\*\*(FR-\d+)\*\*\s*\(([^)]+)\):\s*(.*)/);
|
|
54
|
+
if (match)
|
|
55
|
+
return { id: match[1], priority: match[2], description: match[3].trim() };
|
|
56
|
+
return { id: 'FR-???', priority: 'must', description: l };
|
|
57
|
+
});
|
|
58
|
+
// NFRs
|
|
59
|
+
const nfrLines = extractSection('4. Non-Functional Requirements');
|
|
60
|
+
const getNfr = (key) => {
|
|
61
|
+
const line = nfrLines.find((l) => l.includes(key));
|
|
62
|
+
if (!line)
|
|
63
|
+
return 'N/A';
|
|
64
|
+
const idx = line.indexOf('—');
|
|
65
|
+
return idx !== -1 ? line.slice(idx + 1).trim() : line.replace(/.*:/, '').trim();
|
|
66
|
+
};
|
|
67
|
+
const nfrStack = getNfr('Stack');
|
|
68
|
+
const nfrDatabase = getNfr('Database');
|
|
69
|
+
const nfrAuth = getNfr('Authentication');
|
|
70
|
+
const nfrScale = getNfr('Scale');
|
|
71
|
+
const nfrDeploy = getNfr('Deployment');
|
|
72
|
+
const nfrPerf = getNfr('Performance');
|
|
73
|
+
const nfrObs = getNfr('Observability');
|
|
74
|
+
// Out of scope
|
|
75
|
+
const outOfScope = extractSection('5. Out of Scope')
|
|
76
|
+
.filter((l) => l.startsWith('- '))
|
|
77
|
+
.map((l) => l.slice(2).trim());
|
|
78
|
+
// Acceptance criteria
|
|
79
|
+
const acceptanceCriteria = extractSection('6. Product Acceptance Criteria')
|
|
80
|
+
.filter((l) => l.startsWith('- [ ]'))
|
|
81
|
+
.map((l) => l.replace('- [ ]', '').trim());
|
|
82
|
+
// Assumptions + risks
|
|
83
|
+
const assRiskLines = extractSection('7. Assumptions and Risks');
|
|
84
|
+
const assumptionsLine = assRiskLines.find((l) => l.startsWith('- **Assumptions**'));
|
|
85
|
+
const risksLine = assRiskLines.find((l) => l.startsWith('- **Risks**'));
|
|
86
|
+
const assumptions = assumptionsLine
|
|
87
|
+
? assumptionsLine.replace('- **Assumptions**:', '').trim()
|
|
88
|
+
: '';
|
|
89
|
+
const risks = risksLine ? risksLine.replace('- **Risks**:', '').trim() : '';
|
|
90
|
+
return {
|
|
91
|
+
projectName,
|
|
92
|
+
overview,
|
|
93
|
+
personas,
|
|
94
|
+
functionalReqs,
|
|
95
|
+
nfrStack,
|
|
96
|
+
nfrDatabase,
|
|
97
|
+
nfrAuth,
|
|
98
|
+
nfrScale,
|
|
99
|
+
nfrDeploy,
|
|
100
|
+
nfrPerf,
|
|
101
|
+
nfrObs,
|
|
102
|
+
outOfScope,
|
|
103
|
+
acceptanceCriteria,
|
|
104
|
+
assumptions,
|
|
105
|
+
risks,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// FR → technical requirement mapping
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
function mapFrToTechReqs(fr) {
|
|
112
|
+
// Each FR becomes at least one tech requirement.
|
|
113
|
+
// Must-have FRs get an implementation + test requirement.
|
|
114
|
+
const reqs = [];
|
|
115
|
+
let idx = 1;
|
|
116
|
+
for (const f of fr) {
|
|
117
|
+
reqs.push(`${idx}. Implement **${f.id}** (${f.priority}): ${f.description}`);
|
|
118
|
+
idx++;
|
|
119
|
+
if (f.priority === 'must') {
|
|
120
|
+
reqs.push(`${idx}. Write tests for ${f.id}: cover happy path, edge cases, and error handling`);
|
|
121
|
+
idx++;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return reqs;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Reads PRD.md content, parses it, and renders SPEC.md.
|
|
128
|
+
* Returns the rendered markdown string.
|
|
129
|
+
*/
|
|
130
|
+
export function generateSpec(prdContent, options = {}) {
|
|
131
|
+
const date = options.date ?? new Date().toISOString().slice(0, 10);
|
|
132
|
+
const outputPath = options.outputPath ?? path.join(process.cwd(), 'SPEC.md');
|
|
133
|
+
// Validate that the template exists
|
|
134
|
+
const templatesDir = options.templatesDir ?? findTemplatesDir();
|
|
135
|
+
const templatePath = path.join(templatesDir, 'SPEC-TEMPLATE.md');
|
|
136
|
+
if (!fs.existsSync(templatePath)) {
|
|
137
|
+
throw new Error(`SPEC-TEMPLATE.md not found at: ${templatePath}`);
|
|
138
|
+
}
|
|
139
|
+
const prd = parsePrd(prdContent);
|
|
140
|
+
const techReqs = mapFrToTechReqs(prd.functionalReqs);
|
|
141
|
+
// Detect stack profile from NFRs
|
|
142
|
+
const stackLine = prd.nfrStack.toLowerCase();
|
|
143
|
+
const isNode = stackLine.includes('node') || stackLine.includes('typescript');
|
|
144
|
+
const isPython = stackLine.includes('python');
|
|
145
|
+
const isGo = stackLine.includes('go');
|
|
146
|
+
const isRust = stackLine.includes('rust');
|
|
147
|
+
const testFramework = isNode
|
|
148
|
+
? 'Vitest'
|
|
149
|
+
: isPython
|
|
150
|
+
? 'pytest'
|
|
151
|
+
: isGo
|
|
152
|
+
? 'go test'
|
|
153
|
+
: isRust
|
|
154
|
+
? 'cargo test'
|
|
155
|
+
: 'Unit tests';
|
|
156
|
+
// Personas section
|
|
157
|
+
const personaRows = prd.personas.length
|
|
158
|
+
? prd.personas.map((p) => `| ${p} | Core persona | Product requirements |`).join('\n')
|
|
159
|
+
: '| user | Primary user | Core functionality |';
|
|
160
|
+
// Module table: one row per must-have FR
|
|
161
|
+
const mustFrs = prd.functionalReqs.filter((f) => f.priority === 'must');
|
|
162
|
+
const moduleRows = mustFrs.length
|
|
163
|
+
? mustFrs
|
|
164
|
+
.map((f) => `| \`${f.description.split(' ').slice(0, 3).join('-').toLowerCase()}\` | ${f.description} |`)
|
|
165
|
+
.join('\n')
|
|
166
|
+
: '| `core` | Core module |';
|
|
167
|
+
// Out of scope
|
|
168
|
+
const outLines = prd.outOfScope.length
|
|
169
|
+
? prd.outOfScope.map((s) => `- ${s}`).join('\n')
|
|
170
|
+
: '- (none specified)';
|
|
171
|
+
// Technical requirements numbered list
|
|
172
|
+
const techReqsBlock = techReqs.join('\n');
|
|
173
|
+
const md = `# SPEC — ${prd.projectName}
|
|
174
|
+
|
|
175
|
+
> Generated by \`hardness spec\` on ${date} from PRD.md.
|
|
176
|
+
> This document is the input for \`hardness plan\`. The \`specLines\` in each feature reference lines from this file.
|
|
177
|
+
|
|
178
|
+
## 1. Architectural Vision
|
|
179
|
+
|
|
180
|
+
${prd.overview || 'See PRD.md for the product vision.'}
|
|
181
|
+
|
|
182
|
+
\`\`\`
|
|
183
|
+
${prd.projectName}
|
|
184
|
+
+-- Core features: ${mustFrs.map((f) => f.description).join(', ')}
|
|
185
|
+
+-- Stack: ${prd.nfrStack}
|
|
186
|
+
\`\`\`
|
|
187
|
+
|
|
188
|
+
## 2. Tech Stack
|
|
189
|
+
|
|
190
|
+
- **Language / Runtime**: ${prd.nfrStack}
|
|
191
|
+
- **Tests**: ${testFramework}
|
|
192
|
+
- **Database**: ${prd.nfrDatabase}
|
|
193
|
+
- **Authentication**: ${prd.nfrAuth}
|
|
194
|
+
- **Deployment**: ${prd.nfrDeploy}
|
|
195
|
+
- **Scale**: ${prd.nfrScale}
|
|
196
|
+
- **Performance**: ${prd.nfrPerf}
|
|
197
|
+
- **Observability**: ${prd.nfrObs}
|
|
198
|
+
|
|
199
|
+
## 3. Modules / Packages
|
|
200
|
+
|
|
201
|
+
| Module | Responsibility |
|
|
202
|
+
|---|---|
|
|
203
|
+
${moduleRows}
|
|
204
|
+
|
|
205
|
+
## 4. Data Model
|
|
206
|
+
|
|
207
|
+
${prd.personas.length ? `Personas: ${prd.personas.join(', ')}.` : 'See PRD.md for personas.'}
|
|
208
|
+
|
|
209
|
+
${prd.functionalReqs.length
|
|
210
|
+
? `Entities implied by functional requirements:\n${prd.functionalReqs
|
|
211
|
+
.filter((f) => f.priority === 'must')
|
|
212
|
+
.map((f) => `- **${f.id}**: ${f.description}`)
|
|
213
|
+
.join('\n')}`
|
|
214
|
+
: ''}
|
|
215
|
+
|
|
216
|
+
## 5. Main Flows
|
|
217
|
+
|
|
218
|
+
${prd.functionalReqs
|
|
219
|
+
.filter((f) => f.priority === 'must')
|
|
220
|
+
.map((f, i) => `### 5.${i + 1} ${f.description}
|
|
221
|
+
1. Trigger the ${f.description.toLowerCase()} flow
|
|
222
|
+
2. Validate inputs and apply business rules
|
|
223
|
+
3. Persist changes and return result`)
|
|
224
|
+
.join('\n\n')}
|
|
225
|
+
|
|
226
|
+
## 6. Integration Contracts
|
|
227
|
+
|
|
228
|
+
- **Out of scope (v1.0)**: ${prd.outOfScope.join('; ') || 'None specified'}
|
|
229
|
+
- **Assumptions**: ${prd.assumptions || 'Standard runtime environment'}
|
|
230
|
+
- **Risks**: ${prd.risks || 'See PRD.md'}
|
|
231
|
+
|
|
232
|
+
## 7. Detailed Technical Requirements
|
|
233
|
+
|
|
234
|
+
(The lines below will be referenced by \`specLines\` in the sprints.)
|
|
235
|
+
|
|
236
|
+
${techReqsBlock}
|
|
237
|
+
|
|
238
|
+
### Out of Scope
|
|
239
|
+
|
|
240
|
+
${outLines}
|
|
241
|
+
`;
|
|
242
|
+
fs.writeFileSync(outputPath, md, 'utf-8');
|
|
243
|
+
return md;
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=spec-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-generator.js","sourceRoot":"","sources":["../../src/generators/spec-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface TechRequirement {
|
|
2
|
+
/** 1-indexed position in section 7 */
|
|
3
|
+
index: number;
|
|
4
|
+
/** Full text of the requirement (without leading "N. ") */
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseSpec(content: string): {
|
|
8
|
+
projectName: string;
|
|
9
|
+
techReqs: TechRequirement[];
|
|
10
|
+
stack: string;
|
|
11
|
+
};
|
|
12
|
+
export interface GeneratedFeature {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
description: string;
|
|
16
|
+
specLines: string;
|
|
17
|
+
status: 'pending';
|
|
18
|
+
files: string[];
|
|
19
|
+
acceptanceCriteria: string[];
|
|
20
|
+
hints: string[];
|
|
21
|
+
verification: {
|
|
22
|
+
typecheck: boolean;
|
|
23
|
+
smoke: {
|
|
24
|
+
command: string;
|
|
25
|
+
executedBy: string;
|
|
26
|
+
timeoutSeconds: number;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface GeneratedSprint {
|
|
31
|
+
index: number;
|
|
32
|
+
name: string;
|
|
33
|
+
status: 'pending';
|
|
34
|
+
description: string;
|
|
35
|
+
crossCutting: string[];
|
|
36
|
+
features: GeneratedFeature[];
|
|
37
|
+
}
|
|
38
|
+
export interface SprintGeneratorOptions {
|
|
39
|
+
/** Directory where sprint JSON files will be written. Defaults to .hardness/sprints/ relative to root. */
|
|
40
|
+
sprintsDir?: string;
|
|
41
|
+
/** Project root to locate .hardness/. */
|
|
42
|
+
root?: string;
|
|
43
|
+
/** Dry run — return sprints without writing files. */
|
|
44
|
+
dryRun?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface SprintGeneratorResult {
|
|
47
|
+
sprints: GeneratedSprint[];
|
|
48
|
+
indexJson: object;
|
|
49
|
+
sprintFiles: string[];
|
|
50
|
+
}
|
|
51
|
+
export declare function generateSprints(specContent: string, options?: SprintGeneratorOptions): SprintGeneratorResult;
|