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.
Files changed (52) hide show
  1. package/AGENTS.md +11 -0
  2. package/CHANGELOG.md +36 -0
  3. package/README.md +62 -15
  4. package/node_modules/@hardness/analyzers/package.json +1 -1
  5. package/node_modules/@hardness/core/dist/common/paths.js +2 -2
  6. package/node_modules/@hardness/core/dist/common/paths.js.map +1 -1
  7. package/node_modules/@hardness/core/package.json +1 -1
  8. package/node_modules/@hardness/prompts/package.json +1 -1
  9. package/package.json +1 -1
  10. package/packages/analyzers/package.json +1 -1
  11. package/packages/cli/dist/commands/discover.js +47 -6
  12. package/packages/cli/dist/commands/discover.js.map +1 -1
  13. package/packages/cli/dist/commands/plan.js +39 -6
  14. package/packages/cli/dist/commands/plan.js.map +1 -1
  15. package/packages/cli/dist/commands/spec.js +34 -6
  16. package/packages/cli/dist/commands/spec.js.map +1 -1
  17. package/packages/cli/dist/dispatcher.d.ts +2 -0
  18. package/packages/cli/dist/dispatcher.js +63 -62
  19. package/packages/cli/dist/dispatcher.js.map +1 -1
  20. package/packages/cli/dist/generators/prd-generator.d.ts +14 -0
  21. package/packages/cli/dist/generators/prd-generator.js +164 -0
  22. package/packages/cli/dist/generators/prd-generator.js.map +1 -0
  23. package/packages/cli/dist/generators/spec-generator.d.ts +35 -0
  24. package/packages/cli/dist/generators/spec-generator.js +245 -0
  25. package/packages/cli/dist/generators/spec-generator.js.map +1 -0
  26. package/packages/cli/dist/generators/sprint-generator.d.ts +51 -0
  27. package/packages/cli/dist/generators/sprint-generator.js +162 -0
  28. package/packages/cli/dist/generators/sprint-generator.js.map +1 -0
  29. package/packages/cli/dist/index.js +1 -1
  30. package/packages/cli/dist/interview/evaluator-prompt.d.ts +9 -0
  31. package/packages/cli/dist/interview/evaluator-prompt.js +192 -0
  32. package/packages/cli/dist/interview/evaluator-prompt.js.map +1 -0
  33. package/packages/cli/dist/interview/evaluator.d.ts +46 -0
  34. package/packages/cli/dist/interview/evaluator.js +142 -0
  35. package/packages/cli/dist/interview/evaluator.js.map +1 -0
  36. package/packages/cli/dist/interview/questions.d.ts +29 -0
  37. package/packages/cli/dist/interview/questions.js +642 -0
  38. package/packages/cli/dist/interview/questions.js.map +1 -0
  39. package/packages/cli/dist/interview/runner.d.ts +14 -0
  40. package/packages/cli/dist/interview/runner.js +327 -0
  41. package/packages/cli/dist/interview/runner.js.map +1 -0
  42. package/packages/cli/dist/interview/suggestions.d.ts +6 -0
  43. package/packages/cli/dist/interview/suggestions.js +230 -0
  44. package/packages/cli/dist/interview/suggestions.js.map +1 -0
  45. package/packages/cli/dist/interview/types.d.ts +46 -0
  46. package/packages/cli/dist/interview/types.js +50 -0
  47. package/packages/cli/dist/interview/types.js.map +1 -0
  48. package/packages/cli/package.json +1 -1
  49. package/packages/core/dist/common/paths.js +2 -2
  50. package/packages/core/dist/common/paths.js.map +1 -1
  51. package/packages/core/package.json +1 -1
  52. package/packages/prompts/package.json +1 -1
@@ -0,0 +1,162 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ export function parseSpec(content) {
4
+ const lines = content.split('\n');
5
+ // Project name from h1
6
+ const h1 = lines.find((l) => l.startsWith('# SPEC —'));
7
+ const projectName = h1 ? h1.replace('# SPEC —', '').trim() : 'Unknown Project';
8
+ // Stack from section 2
9
+ const stackLine = lines.find((l) => l.includes('**Language / Runtime**') || l.includes('**Language**'));
10
+ const stack = stackLine ? stackLine.replace(/.*?—/, '').trim() : 'Node.js / TypeScript';
11
+ // Technical requirements from section 7
12
+ const sec7Start = lines.findIndex((l) => l.startsWith('## 7. Detailed Technical Requirements'));
13
+ const techReqs = [];
14
+ if (sec7Start !== -1) {
15
+ let idx = 1;
16
+ for (let i = sec7Start + 1; i < lines.length; i++) {
17
+ const line = lines[i];
18
+ if (line.startsWith('## '))
19
+ break; // next section
20
+ // Match "N. text" or "N) text"
21
+ const match = line.match(/^\d+[.)]\s+(.+)/);
22
+ if (match) {
23
+ techReqs.push({ index: idx++, text: match[1].trim() });
24
+ }
25
+ }
26
+ }
27
+ return { projectName, techReqs, stack };
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // Sprint builder
31
+ // ---------------------------------------------------------------------------
32
+ /** How many features to group per sprint (2-4) */
33
+ const FEATURES_PER_SPRINT = 3;
34
+ function deriveFilesFromReq(text, projectName) {
35
+ // Heuristic: look for file paths in the requirement text
36
+ const files = [];
37
+ const fileMatch = text.match(/`([^`]+\.[a-z]+)`/g);
38
+ if (fileMatch) {
39
+ fileMatch.forEach((m) => files.push(m.replace(/`/g, '')));
40
+ }
41
+ if (files.length === 0) {
42
+ // Default: derive a plausible file from the requirement text
43
+ const slug = text
44
+ .toLowerCase()
45
+ .replace(/[^a-z0-9\s]/g, '')
46
+ .split(/\s+/)
47
+ .slice(0, 4)
48
+ .join('-');
49
+ files.push(`src/${slug}.ts`);
50
+ }
51
+ return [...new Set(files)];
52
+ }
53
+ function buildFeature(req, featId) {
54
+ const files = deriveFilesFromReq(req.text, '');
55
+ // Determine if this is a test requirement
56
+ const isTest = req.text.toLowerCase().includes('test') || req.text.toLowerCase().includes('write tests');
57
+ const acceptance = [
58
+ `${req.text} is implemented correctly`,
59
+ 'npm run build compiles without errors',
60
+ 'npm test passes',
61
+ ];
62
+ const hints = [
63
+ `Section 7, requirement ${req.index} in SPEC.md`,
64
+ ];
65
+ if (isTest) {
66
+ hints.push('Use HARDNESS_ROOT env var pattern for fixture directories in tests');
67
+ acceptance[0] = `Tests for ${req.text.replace(/^Write tests for\s*/i, '')} cover all edge cases`;
68
+ }
69
+ return {
70
+ id: featId,
71
+ title: req.text.length > 60 ? req.text.slice(0, 57) + '...' : req.text,
72
+ description: req.text,
73
+ specLines: `${req.index}`,
74
+ status: 'pending',
75
+ files,
76
+ acceptanceCriteria: acceptance,
77
+ hints,
78
+ verification: {
79
+ typecheck: true,
80
+ smoke: {
81
+ command: 'npm test',
82
+ executedBy: 'workflow',
83
+ timeoutSeconds: 60,
84
+ },
85
+ },
86
+ };
87
+ }
88
+ export function generateSprints(specContent, options = {}) {
89
+ const root = options.root ?? process.env.HARDNESS_ROOT ?? process.cwd();
90
+ const sprintsDir = options.sprintsDir ?? path.join(root, '.hardness', 'sprints');
91
+ const { projectName, techReqs, stack } = parseSpec(specContent);
92
+ if (techReqs.length === 0) {
93
+ throw new Error('No technical requirements found in SPEC.md section 7. ' +
94
+ 'Make sure section 7 has numbered requirements (e.g. "1. ...").');
95
+ }
96
+ // Group requirements into sprints of FEATURES_PER_SPRINT each
97
+ const sprints = [];
98
+ const chunks = [];
99
+ for (let i = 0; i < techReqs.length; i += FEATURES_PER_SPRINT) {
100
+ chunks.push(techReqs.slice(i, i + FEATURES_PER_SPRINT));
101
+ }
102
+ const stackLabel = stack.toLowerCase().includes('node')
103
+ ? 'TypeScript strict mode, ESM, no console.log in production code'
104
+ : stack.toLowerCase().includes('python')
105
+ ? 'Python 3.10+, type hints required, no print() in production code'
106
+ : 'Strict types, no debug output in production code';
107
+ chunks.forEach((chunk, sprintIdx) => {
108
+ const sprintNum = String(sprintIdx + 1).padStart(2, '0');
109
+ const features = chunk.map((req, featIdx) => {
110
+ const featNum = String(sprintIdx * FEATURES_PER_SPRINT + featIdx + 1).padStart(3, '0');
111
+ return buildFeature(req, `feat-${featNum}`);
112
+ });
113
+ sprints.push({
114
+ index: sprintIdx,
115
+ name: `Sprint ${sprintNum} — ${chunk[0].text.slice(0, 40)}`,
116
+ status: 'pending',
117
+ description: `Technical requirements ${chunk[0].index}–${chunk[chunk.length - 1].index} from SPEC.md.`,
118
+ crossCutting: [
119
+ 'All new code must have tests (vitest or equivalent)',
120
+ stackLabel,
121
+ 'No new runtime dependencies without review',
122
+ ],
123
+ features,
124
+ });
125
+ });
126
+ // Build index.json
127
+ const indexJson = {
128
+ projectName,
129
+ specPath: 'SPEC.md',
130
+ totalSprints: sprints.length,
131
+ schemaVersion: 1,
132
+ description: `Sprint plan generated by \`hardness plan\` from SPEC.md.`,
133
+ sprints: sprints.map((s, i) => ({
134
+ index: i,
135
+ file: `${String(i + 1).padStart(2, '0')}-${s.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-+$/, '')}.json`,
136
+ name: s.name,
137
+ status: 'pending',
138
+ featuresCount: s.features.length,
139
+ })),
140
+ };
141
+ const sprintFiles = indexJson.sprints.map((e) => e.file);
142
+ if (!options.dryRun) {
143
+ // Ensure sprints dir exists
144
+ if (!fs.existsSync(sprintsDir)) {
145
+ fs.mkdirSync(sprintsDir, { recursive: true });
146
+ }
147
+ // Write each sprint file
148
+ sprints.forEach((sprint, i) => {
149
+ const fileName = sprintFiles[i];
150
+ const filePath = path.join(sprintsDir, fileName);
151
+ fs.writeFileSync(filePath, JSON.stringify(sprint, null, 2), 'utf-8');
152
+ });
153
+ // Write index
154
+ const indexPath = path.join(sprintsDir, '00-index.json');
155
+ fs.writeFileSync(indexPath, JSON.stringify(indexJson, null, 2), 'utf-8');
156
+ // Set current.txt to first sprint
157
+ const currentPath = path.join(path.dirname(sprintsDir), 'current.txt');
158
+ fs.writeFileSync(currentPath, sprintFiles[0], 'utf-8');
159
+ }
160
+ return { sprints, indexJson, sprintFiles };
161
+ }
162
+ //# sourceMappingURL=sprint-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sprint-generator.js","sourceRoot":"","sources":["../../src/generators/sprint-generator.ts"],"names":[],"mappings":"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"}
@@ -18,7 +18,7 @@ export function buildCli() {
18
18
  program
19
19
  .name('hardness')
20
20
  .description('Hardness — Universal Agentic Development Harness')
21
- .version('1.0.0');
21
+ .version('1.1.0');
22
22
  initCommand(program);
23
23
  validateCommand(program);
24
24
  scoreCommand(program);
@@ -0,0 +1,9 @@
1
+ import type { UserLevel } from './types.js';
2
+ import type { Question } from './questions.js';
3
+ export declare function buildTranslationPrompt(locale: string, questions: Array<{
4
+ id: string;
5
+ prompt: string;
6
+ choices?: string[];
7
+ }>): string;
8
+ export declare function buildEvaluationPrompt(phase: string, userLevel: UserLevel, locale: string, answers: Record<string, string | string[]>, allQuestions: Question[], detectedStack: string): string;
9
+ export declare function buildPrdGenerationPrompt(allAnswers: Record<string, string | string[]>, userLevel: UserLevel, locale: string, detectedStack: string, projectName: string): string;
@@ -0,0 +1,192 @@
1
+ // Evaluator prompts for the interview specialist agent.
2
+ // These are sent to the user's LLM (via agentCommand) for:
3
+ // 1. Translating questions to the user's locale
4
+ // 2. Evaluating phase completeness and generating follow-up questions
5
+ // 3. Generating the final PRD document
6
+ // ---------------------------------------------------------------------------
7
+ // Jargon-free language rules (embedded in all prompts that generate content
8
+ // for beginners)
9
+ // ---------------------------------------------------------------------------
10
+ const BEGINNER_LANGUAGE_RULES = `
11
+ CRITICAL LANGUAGE RULES FOR BEGINNER LEVEL:
12
+ - Use ZERO technical jargon. No words like: frontend, backend, API, database,
13
+ deploy, framework, authentication, endpoint, container, serverless, OAuth,
14
+ throughput, observability, metrics, tracing, schema, migration, middleware,
15
+ SDK, repository, runtime, dependency, package, module, component, hook,
16
+ state management, CI/CD, DevOps, microservice, monolith, REST, GraphQL,
17
+ WebSocket, token, hash, encryption, SSL, DNS, load balancer, cache.
18
+ - Write as if talking to someone who has never opened a code editor.
19
+ - Explain concepts through their purpose, not their technical name.
20
+ Example: say "where your system saves information" instead of "database".
21
+ Example: say "how people sign in" instead of "authentication strategy".
22
+ Example: say "where your system will run so people can access it" instead of "deployment target".
23
+ `;
24
+ const INTERMEDIATE_LANGUAGE_RULES = `
25
+ LANGUAGE RULES FOR INTERMEDIATE LEVEL:
26
+ - Basic technical terms are allowed (e.g., "database", "login", "server", "hosting").
27
+ - Avoid acronyms and specific tool names without context.
28
+ Example: say "a database like PostgreSQL or MongoDB" instead of just "PostgreSQL".
29
+ Example: say "hosting service like Heroku or Render" instead of "PaaS".
30
+ `;
31
+ const ADVANCED_LANGUAGE_RULES = `
32
+ LANGUAGE RULES FOR ADVANCED LEVEL:
33
+ - Use direct technical language. Name tools, frameworks, and concepts as they are.
34
+ - Be concise. Skip explanations of well-known concepts.
35
+ `;
36
+ function languageRulesFor(level) {
37
+ switch (level) {
38
+ case 'beginner': return BEGINNER_LANGUAGE_RULES;
39
+ case 'intermediate': return INTERMEDIATE_LANGUAGE_RULES;
40
+ case 'advanced': return ADVANCED_LANGUAGE_RULES;
41
+ }
42
+ }
43
+ // ---------------------------------------------------------------------------
44
+ // 1. Translation prompt
45
+ // ---------------------------------------------------------------------------
46
+ export function buildTranslationPrompt(locale, questions) {
47
+ return `You are a professional translator. Translate the following interview questions from English to the locale "${locale}".
48
+
49
+ RULES:
50
+ - Translate naturally, not word-for-word. Adapt idioms and phrasing to sound native.
51
+ - Keep the same tone (friendly, conversational).
52
+ - Preserve all placeholder markers like (?) and formatting instructions.
53
+ - For choice questions, translate each choice option.
54
+ - Do NOT translate question IDs.
55
+
56
+ INPUT (JSON array of questions):
57
+ ${JSON.stringify(questions, null, 2)}
58
+
59
+ OUTPUT FORMAT — return ONLY a valid JSON array with the same structure:
60
+ [
61
+ {
62
+ "id": "<same id>",
63
+ "prompt": "<translated prompt>",
64
+ "choices": ["<translated choice 1>", "<translated choice 2>", ...]
65
+ }
66
+ ]
67
+
68
+ Return ONLY the JSON array, no explanation, no markdown fences.`;
69
+ }
70
+ // ---------------------------------------------------------------------------
71
+ // 2. Phase evaluation prompt
72
+ // ---------------------------------------------------------------------------
73
+ export function buildEvaluationPrompt(phase, userLevel, locale, answers, allQuestions, detectedStack) {
74
+ const langRules = languageRulesFor(userLevel);
75
+ return `You are a specialist PRD (Product Requirements Document) evaluator. Your job is to assess the quality and completeness of information collected during a product discovery interview.
76
+
77
+ ## Context
78
+ - Current phase: "${phase}"
79
+ - User level: "${userLevel}" (${userLevel === 'beginner' ? 'never programmed before' : userLevel === 'intermediate' ? 'some programming experience' : 'professional developer'})
80
+ - User locale: "${locale}"
81
+ - Detected stack: "${detectedStack}"
82
+
83
+ ## Answers collected so far
84
+ ${JSON.stringify(answers, null, 2)}
85
+
86
+ ## Questions asked in this phase
87
+ ${JSON.stringify(allQuestions.filter(q => q.phase === phase).map(q => ({ id: q.id, prompt: q.prompt })), null, 2)}
88
+
89
+ ## Your task
90
+
91
+ 1. **Score** the completeness of the specifications collected for this phase on a scale of 0-10:
92
+ - 0-3: Critical information is missing
93
+ - 4-6: Some important details are missing
94
+ - 7-8: Mostly complete, minor gaps
95
+ - 9-10: Excellent, all important aspects covered
96
+
97
+ 2. **Identify gaps**: What important information is still missing?
98
+
99
+ 3. **Generate follow-up questions** (ONLY if score < 9): Create 1-3 follow-up questions to fill the gaps.
100
+
101
+ ${langRules}
102
+
103
+ ## Follow-up question rules
104
+ - Questions MUST be in the locale "${locale}" (translate if not English)
105
+ - Questions MUST respect the user level language rules above
106
+ - Each question must have a unique ID in the format "q{phase}.extra.{n}" (e.g., "q1.extra.1")
107
+ - Each question must have a type: "string", "multiline", or "choice"
108
+ - For "choice" type, provide a "choices" array
109
+
110
+ ## Output format — return ONLY valid JSON:
111
+ {
112
+ "score": <number 0-10>,
113
+ "gaps": ["<gap description 1>", "<gap description 2>"],
114
+ "followUpQuestions": [
115
+ {
116
+ "id": "q${phase}.extra.1",
117
+ "prompt": "<question text in the user's locale>",
118
+ "type": "string" | "multiline" | "choice",
119
+ "choices": ["<option1>", "<option2>"] // only for type "choice"
120
+ }
121
+ ],
122
+ "reasoning": "<brief explanation of the score>"
123
+ }
124
+
125
+ Return ONLY the JSON object, no explanation, no markdown fences.`;
126
+ }
127
+ // ---------------------------------------------------------------------------
128
+ // 3. PRD generation prompt
129
+ // ---------------------------------------------------------------------------
130
+ export function buildPrdGenerationPrompt(allAnswers, userLevel, locale, detectedStack, projectName) {
131
+ return `You are a specialist PRD (Product Requirements Document) writer. Generate a professional PRD document in Markdown format based on the interview answers provided.
132
+
133
+ ## CRITICAL RULES — FOLLOW EXACTLY
134
+ 1. Use ONLY information from the answers below. Do NOT invent features, personas, requirements, or technical details that were not mentioned.
135
+ 2. If information for a section is missing, write "To be determined." — do NOT make up content.
136
+ 3. The PRD must be in English regardless of the interview language.
137
+ 4. Follow the EXACT template structure below.
138
+ 5. Every Functional Requirement must have a priority: (must), (should), or (could).
139
+ 6. Non-Functional Requirements must be specific and measurable when possible.
140
+
141
+ ## Interview answers
142
+ ${JSON.stringify(allAnswers, null, 2)}
143
+
144
+ ## Additional context
145
+ - Project name: "${projectName}"
146
+ - User level: "${userLevel}"
147
+ - Detected stack: "${detectedStack}"
148
+ - Interview locale: "${locale}"
149
+
150
+ ## PRD Template — follow this structure EXACTLY
151
+
152
+ # PRD — ${projectName}
153
+
154
+ > Product Requirements Document for ${projectName}.
155
+ > Generated by \`hardness discover\`.
156
+
157
+ ## 1. Overview
158
+
159
+ <Write 2-3 paragraphs based on q0.1 (free text), q1.2 (one-liner), and q1.3 (problem statement). Synthesize into a coherent overview.>
160
+
161
+ ## 2. Personas
162
+
163
+ | Persona | Description | Needs |
164
+ |---|---|---|
165
+ <One row per persona based on q2.1, q2.2, q2.3. Include the "Next AI agent" persona as the last row.>
166
+
167
+ ## 3. Functional Requirements
168
+
169
+ <List each feature from q3.1 as a numbered FR with priority from q3.2. Format: "- **FR-NNN** (priority): description">
170
+
171
+ ## 4. Non-Functional Requirements
172
+
173
+ <Derive from q4.1 through q4.8: stack, frontend, database, auth, scale, deploy, performance, observability. Format: "- **NFR-NNN**: description">
174
+
175
+ ## 5. Out of Scope
176
+
177
+ <List items from q5.1, one per bullet.>
178
+
179
+ ## 6. Product Acceptance Criteria
180
+
181
+ <Derive from q3.3 and the functional requirements. Each criterion must be a checkbox: "- [ ] criterion">
182
+
183
+ ## 7. Assumptions and Risks
184
+
185
+ - **Assumptions**: <from q5.2>
186
+ - **Risks**: <from q5.3>
187
+
188
+ ---
189
+
190
+ Return ONLY the Markdown content of the PRD. No JSON wrapping, no code fences, no explanation.`;
191
+ }
192
+ //# sourceMappingURL=evaluator-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluator-prompt.js","sourceRoot":"","sources":["../../src/interview/evaluator-prompt.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,2DAA2D;AAC3D,gDAAgD;AAChD,sEAAsE;AACtE,uCAAuC;AAKvC,8EAA8E;AAC9E,4EAA4E;AAC5E,iBAAiB;AACjB,8EAA8E;AAE9E,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;CAa/B,CAAC;AAEF,MAAM,2BAA2B,GAAG;;;;;;CAMnC,CAAC;AAEF,MAAM,uBAAuB,GAAG;;;;CAI/B,CAAC;AAEF,SAAS,gBAAgB,CAAC,KAAgB;IACxC,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,UAAU,CAAC,CAAC,OAAO,uBAAuB,CAAC;QAChD,KAAK,cAAc,CAAC,CAAC,OAAO,2BAA2B,CAAC;QACxD,KAAK,UAAU,CAAC,CAAC,OAAO,uBAAuB,CAAC;IAClD,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E,MAAM,UAAU,sBAAsB,CACpC,MAAc,EACd,SAAoE;IAEpE,OAAO,8GAA8G,MAAM;;;;;;;;;;EAU3H,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;;;;;;;;;;;gEAW4B,CAAC;AACjE,CAAC;AAED,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAE9E,MAAM,UAAU,qBAAqB,CACnC,KAAa,EACb,SAAoB,EACpB,MAAc,EACd,OAA0C,EAC1C,YAAwB,EACxB,aAAqB;IAErB,MAAM,SAAS,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAE9C,OAAO;;;oBAGW,KAAK;iBACR,SAAS,MAAM,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,SAAS,KAAK,cAAc,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,wBAAwB;kBAC5J,MAAM;qBACH,aAAa;;;EAGhC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;;;EAGhC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;;;;;;;;;;;;;;EAc/G,SAAS;;;qCAG0B,MAAM;;;;;;;;;;;;gBAY3B,KAAK;;;;;;;;;iEAS4C,CAAC;AAClE,CAAC;AAED,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E,MAAM,UAAU,wBAAwB,CACtC,UAA6C,EAC7C,SAAoB,EACpB,MAAc,EACd,aAAqB,EACrB,WAAmB;IAEnB,OAAO;;;;;;;;;;;EAWP,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;;;mBAGlB,WAAW;iBACb,SAAS;qBACL,aAAa;uBACX,MAAM;;;;UAInB,WAAW;;sCAEiB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+FAoC8C,CAAC;AAChG,CAAC"}
@@ -0,0 +1,46 @@
1
+ import type { InterviewState, PhaseEvaluation } from './types.js';
2
+ import type { Question } from './questions.js';
3
+ export interface TranslatedQuestion {
4
+ id: string;
5
+ prompt: string;
6
+ choices?: string[];
7
+ }
8
+ export interface EvaluatorOptions {
9
+ /** The agent command template from config.json (e.g. 'claude -p "$(cat {context_file})"') */
10
+ agentCommand: string;
11
+ /** Project root directory */
12
+ projectRoot: string;
13
+ /** Timeout in seconds for LLM calls */
14
+ timeout?: number;
15
+ }
16
+ export declare class InterviewEvaluator {
17
+ private agentCommand;
18
+ private projectRoot;
19
+ private timeoutMs;
20
+ constructor(options: EvaluatorOptions);
21
+ /**
22
+ * Sends a prompt to the LLM via agentCommand and returns the raw response.
23
+ * Writes the prompt to a temp file, substitutes {context_file}, and captures stdout.
24
+ */
25
+ private invokeLlm;
26
+ /**
27
+ * Extracts JSON from LLM response, handling possible markdown fences.
28
+ */
29
+ private extractJson;
30
+ /**
31
+ * Translate all questions to the target locale via LLM.
32
+ */
33
+ translateQuestions(locale: string, questions: Array<{
34
+ id: string;
35
+ prompt: string;
36
+ choices?: string[];
37
+ }>): Promise<TranslatedQuestion[]>;
38
+ /**
39
+ * Evaluate the completeness of a phase and optionally generate follow-up questions.
40
+ */
41
+ evaluatePhase(phase: string, state: InterviewState, allQuestions: Question[]): Promise<PhaseEvaluation>;
42
+ /**
43
+ * Generate the final PRD document from all collected answers.
44
+ */
45
+ generatePrd(state: InterviewState): Promise<string>;
46
+ }
@@ -0,0 +1,142 @@
1
+ // Interview evaluator — invokes the user's LLM (via agentCommand) for:
2
+ // 1. Translating questions to the user's locale
3
+ // 2. Evaluating phase completeness and scoring
4
+ // 3. Generating the final PRD
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import { runChecked } from '@hardness/core';
9
+ import { buildTranslationPrompt, buildEvaluationPrompt, buildPrdGenerationPrompt, } from './evaluator-prompt.js';
10
+ // ---------------------------------------------------------------------------
11
+ // Evaluator class
12
+ // ---------------------------------------------------------------------------
13
+ export class InterviewEvaluator {
14
+ agentCommand;
15
+ projectRoot;
16
+ timeoutMs;
17
+ constructor(options) {
18
+ this.agentCommand = options.agentCommand;
19
+ this.projectRoot = options.projectRoot;
20
+ this.timeoutMs = (options.timeout ?? 120) * 1000;
21
+ }
22
+ /**
23
+ * Sends a prompt to the LLM via agentCommand and returns the raw response.
24
+ * Writes the prompt to a temp file, substitutes {context_file}, and captures stdout.
25
+ */
26
+ async invokeLlm(prompt) {
27
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hardness-eval-'));
28
+ const promptFile = path.join(tmpDir, 'prompt.txt');
29
+ try {
30
+ fs.writeFileSync(promptFile, prompt, 'utf-8');
31
+ const normalizedPath = promptFile.split('\\').join('/');
32
+ const normalizedCwd = this.projectRoot.split('\\').join('/');
33
+ const command = this.agentCommand
34
+ .split('{context_file}').join(normalizedPath)
35
+ .split('{cwd}').join(normalizedCwd)
36
+ .split('{timeout}').join(String(Math.floor(this.timeoutMs / 1000)));
37
+ const result = await runChecked(command, {
38
+ cwd: this.projectRoot,
39
+ timeoutMs: this.timeoutMs,
40
+ });
41
+ if (result.error) {
42
+ throw new Error(`LLM invocation failed: ${result.error.message}`);
43
+ }
44
+ if (result.code !== 0) {
45
+ throw new Error(`LLM exited with code ${result.code}: ${result.stderr}`);
46
+ }
47
+ return result.stdout.trim();
48
+ }
49
+ finally {
50
+ try {
51
+ fs.rmSync(tmpDir, { recursive: true, force: true });
52
+ }
53
+ catch {
54
+ // best-effort cleanup
55
+ }
56
+ }
57
+ }
58
+ /**
59
+ * Extracts JSON from LLM response, handling possible markdown fences.
60
+ */
61
+ extractJson(raw) {
62
+ // Strip markdown code fences if present
63
+ const fenceMatch = raw.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
64
+ if (fenceMatch) {
65
+ return fenceMatch[1].trim();
66
+ }
67
+ // Try to find JSON object or array directly
68
+ const jsonStart = raw.indexOf('{') !== -1 ? raw.indexOf('{') : raw.indexOf('[');
69
+ const jsonEnd = raw.lastIndexOf('}') !== -1 ? raw.lastIndexOf('}') : raw.lastIndexOf(']');
70
+ if (jsonStart !== -1 && jsonEnd !== -1 && jsonEnd > jsonStart) {
71
+ return raw.slice(jsonStart, jsonEnd + 1);
72
+ }
73
+ return raw;
74
+ }
75
+ /**
76
+ * Translate all questions to the target locale via LLM.
77
+ */
78
+ async translateQuestions(locale, questions) {
79
+ const prompt = buildTranslationPrompt(locale, questions);
80
+ const raw = await this.invokeLlm(prompt);
81
+ const json = this.extractJson(raw);
82
+ try {
83
+ const translated = JSON.parse(json);
84
+ if (!Array.isArray(translated)) {
85
+ throw new Error('Expected an array of translated questions');
86
+ }
87
+ return translated;
88
+ }
89
+ catch (e) {
90
+ throw new Error(`Failed to parse translation response: ${e.message}\nRaw: ${raw.slice(0, 500)}`);
91
+ }
92
+ }
93
+ /**
94
+ * Evaluate the completeness of a phase and optionally generate follow-up questions.
95
+ */
96
+ async evaluatePhase(phase, state, allQuestions) {
97
+ const prompt = buildEvaluationPrompt(phase, state.userLevel, state.locale, state.answers, allQuestions, state.detectedStack.label);
98
+ const raw = await this.invokeLlm(prompt);
99
+ const json = this.extractJson(raw);
100
+ try {
101
+ const evaluation = JSON.parse(json);
102
+ if (typeof evaluation.score !== 'number' || !Array.isArray(evaluation.gaps)) {
103
+ throw new Error('Invalid evaluation format');
104
+ }
105
+ // Ensure followUpQuestions is always an array
106
+ if (!Array.isArray(evaluation.followUpQuestions)) {
107
+ evaluation.followUpQuestions = [];
108
+ }
109
+ // Cap at 3 follow-up questions
110
+ evaluation.followUpQuestions = evaluation.followUpQuestions.slice(0, 3);
111
+ return evaluation;
112
+ }
113
+ catch (e) {
114
+ throw new Error(`Failed to parse evaluation response: ${e.message}\nRaw: ${raw.slice(0, 500)}`);
115
+ }
116
+ }
117
+ /**
118
+ * Generate the final PRD document from all collected answers.
119
+ */
120
+ async generatePrd(state) {
121
+ const projectName = state.answers['q1.1'] ?? 'Untitled Project';
122
+ const prompt = buildPrdGenerationPrompt(state.answers, state.userLevel, state.locale, state.detectedStack.label, projectName);
123
+ const raw = await this.invokeLlm(prompt);
124
+ // The response should be raw markdown (no JSON wrapping)
125
+ // Strip markdown fences if present
126
+ let prd = raw;
127
+ if (prd.startsWith('```markdown')) {
128
+ prd = prd.slice('```markdown'.length);
129
+ }
130
+ else if (prd.startsWith('```md')) {
131
+ prd = prd.slice('```md'.length);
132
+ }
133
+ else if (prd.startsWith('```')) {
134
+ prd = prd.slice(3);
135
+ }
136
+ if (prd.endsWith('```')) {
137
+ prd = prd.slice(0, -3);
138
+ }
139
+ return prd.trim();
140
+ }
141
+ }
142
+ //# sourceMappingURL=evaluator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluator.js","sourceRoot":"","sources":["../../src/interview/evaluator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { UserLevel } from './types.js';
2
+ export type QuestionPhase = 'opening' | 'vision' | 'people' | 'features' | 'constraints' | 'boundaries' | 'review';
3
+ export type QuestionType = 'string' | 'multiline' | 'choice' | 'action';
4
+ export interface PromptByLevel {
5
+ beginner: string;
6
+ intermediate: string;
7
+ advanced: string;
8
+ }
9
+ export interface Question {
10
+ id: string;
11
+ phase: QuestionPhase;
12
+ prompt: string;
13
+ type: QuestionType;
14
+ /** Only for type === 'choice' */
15
+ choices?: string[];
16
+ /** Choices adapted per level (only for type === 'choice') */
17
+ choicesByLevel?: Record<UserLevel, string[]>;
18
+ /** Key into the SuggestionEngine (used to call suggest(suggestionId, state)) */
19
+ suggestionId: string;
20
+ /** Prompt text adapted per user level. Falls back to `prompt` if not set. */
21
+ promptByLevel?: PromptByLevel;
22
+ }
23
+ /** Get the prompt for a question based on user level */
24
+ export declare function getPromptForLevel(question: Question, level: UserLevel): string;
25
+ /** Get the choices for a question based on user level */
26
+ export declare function getChoicesForLevel(question: Question, level: UserLevel): string[] | undefined;
27
+ export declare const QUESTIONS: Question[];
28
+ /** Questions grouped by phase for easy iteration */
29
+ export declare function questionsByPhase(phase: QuestionPhase): Question[];