smartlisa 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.
Files changed (155) hide show
  1. package/README.md +77 -0
  2. package/dist/cli.js +6517 -0
  3. package/dist/src/adapters/cli/formatter.d.ts +34 -0
  4. package/dist/src/adapters/cli/formatter.d.ts.map +1 -0
  5. package/dist/src/adapters/cli/formatter.js +254 -0
  6. package/dist/src/adapters/cli/formatter.js.map +1 -0
  7. package/dist/src/adapters/cli/index.d.ts +14 -0
  8. package/dist/src/adapters/cli/index.d.ts.map +1 -0
  9. package/dist/src/adapters/cli/index.js +781 -0
  10. package/dist/src/adapters/cli/index.js.map +1 -0
  11. package/dist/src/adapters/state/__tests__/api.test.d.ts +8 -0
  12. package/dist/src/adapters/state/__tests__/api.test.d.ts.map +1 -0
  13. package/dist/src/adapters/state/__tests__/api.test.js +500 -0
  14. package/dist/src/adapters/state/__tests__/api.test.js.map +1 -0
  15. package/dist/src/adapters/state/__tests__/filesystem.test.d.ts +7 -0
  16. package/dist/src/adapters/state/__tests__/filesystem.test.d.ts.map +1 -0
  17. package/dist/src/adapters/state/__tests__/filesystem.test.js +418 -0
  18. package/dist/src/adapters/state/__tests__/filesystem.test.js.map +1 -0
  19. package/dist/src/adapters/state/api.d.ts +148 -0
  20. package/dist/src/adapters/state/api.d.ts.map +1 -0
  21. package/dist/src/adapters/state/api.js +337 -0
  22. package/dist/src/adapters/state/api.js.map +1 -0
  23. package/dist/src/adapters/state/filesystem.d.ts +80 -0
  24. package/dist/src/adapters/state/filesystem.d.ts.map +1 -0
  25. package/dist/src/adapters/state/filesystem.js +228 -0
  26. package/dist/src/adapters/state/filesystem.js.map +1 -0
  27. package/dist/src/adapters/state/index.d.ts +9 -0
  28. package/dist/src/adapters/state/index.d.ts.map +1 -0
  29. package/dist/src/adapters/state/index.js +10 -0
  30. package/dist/src/adapters/state/index.js.map +1 -0
  31. package/dist/src/adapters/state/types.d.ts +131 -0
  32. package/dist/src/adapters/state/types.d.ts.map +1 -0
  33. package/dist/src/adapters/state/types.js +11 -0
  34. package/dist/src/adapters/state/types.js.map +1 -0
  35. package/dist/src/core/__tests__/context.test.d.ts +12 -0
  36. package/dist/src/core/__tests__/context.test.d.ts.map +1 -0
  37. package/dist/src/core/__tests__/context.test.js +528 -0
  38. package/dist/src/core/__tests__/context.test.js.map +1 -0
  39. package/dist/src/core/__tests__/discover.test.d.ts +2 -0
  40. package/dist/src/core/__tests__/discover.test.d.ts.map +1 -0
  41. package/dist/src/core/__tests__/discover.test.js +667 -0
  42. package/dist/src/core/__tests__/discover.test.js.map +1 -0
  43. package/dist/src/core/__tests__/engine.test.d.ts +2 -0
  44. package/dist/src/core/__tests__/engine.test.d.ts.map +1 -0
  45. package/dist/src/core/__tests__/engine.test.js +444 -0
  46. package/dist/src/core/__tests__/engine.test.js.map +1 -0
  47. package/dist/src/core/__tests__/feedback.test.d.ts +2 -0
  48. package/dist/src/core/__tests__/feedback.test.d.ts.map +1 -0
  49. package/dist/src/core/__tests__/feedback.test.js +351 -0
  50. package/dist/src/core/__tests__/feedback.test.js.map +1 -0
  51. package/dist/src/core/__tests__/plan.test.d.ts +2 -0
  52. package/dist/src/core/__tests__/plan.test.d.ts.map +1 -0
  53. package/dist/src/core/__tests__/plan.test.js +429 -0
  54. package/dist/src/core/__tests__/plan.test.js.map +1 -0
  55. package/dist/src/core/__tests__/schemas.test.d.ts +2 -0
  56. package/dist/src/core/__tests__/schemas.test.d.ts.map +1 -0
  57. package/dist/src/core/__tests__/schemas.test.js +614 -0
  58. package/dist/src/core/__tests__/schemas.test.js.map +1 -0
  59. package/dist/src/core/__tests__/state.test.d.ts +2 -0
  60. package/dist/src/core/__tests__/state.test.d.ts.map +1 -0
  61. package/dist/src/core/__tests__/state.test.js +1107 -0
  62. package/dist/src/core/__tests__/state.test.js.map +1 -0
  63. package/dist/src/core/__tests__/status.test.d.ts +2 -0
  64. package/dist/src/core/__tests__/status.test.d.ts.map +1 -0
  65. package/dist/src/core/__tests__/status.test.js +600 -0
  66. package/dist/src/core/__tests__/status.test.js.map +1 -0
  67. package/dist/src/core/__tests__/test-helpers.d.ts +28 -0
  68. package/dist/src/core/__tests__/test-helpers.d.ts.map +1 -0
  69. package/dist/src/core/__tests__/test-helpers.js +185 -0
  70. package/dist/src/core/__tests__/test-helpers.js.map +1 -0
  71. package/dist/src/core/__tests__/utils.test.d.ts +2 -0
  72. package/dist/src/core/__tests__/utils.test.d.ts.map +1 -0
  73. package/dist/src/core/__tests__/utils.test.js +276 -0
  74. package/dist/src/core/__tests__/utils.test.js.map +1 -0
  75. package/dist/src/core/__tests__/validate.test.d.ts +2 -0
  76. package/dist/src/core/__tests__/validate.test.d.ts.map +1 -0
  77. package/dist/src/core/__tests__/validate.test.js +354 -0
  78. package/dist/src/core/__tests__/validate.test.js.map +1 -0
  79. package/dist/src/core/commands/discover.d.ts +71 -0
  80. package/dist/src/core/commands/discover.d.ts.map +1 -0
  81. package/dist/src/core/commands/discover.js +687 -0
  82. package/dist/src/core/commands/discover.js.map +1 -0
  83. package/dist/src/core/commands/feedback.d.ts +49 -0
  84. package/dist/src/core/commands/feedback.d.ts.map +1 -0
  85. package/dist/src/core/commands/feedback.js +283 -0
  86. package/dist/src/core/commands/feedback.js.map +1 -0
  87. package/dist/src/core/commands/index.d.ts +11 -0
  88. package/dist/src/core/commands/index.d.ts.map +1 -0
  89. package/dist/src/core/commands/index.js +11 -0
  90. package/dist/src/core/commands/index.js.map +1 -0
  91. package/dist/src/core/commands/plan.d.ts +108 -0
  92. package/dist/src/core/commands/plan.d.ts.map +1 -0
  93. package/dist/src/core/commands/plan.js +621 -0
  94. package/dist/src/core/commands/plan.js.map +1 -0
  95. package/dist/src/core/commands/status.d.ts +72 -0
  96. package/dist/src/core/commands/status.d.ts.map +1 -0
  97. package/dist/src/core/commands/status.js +720 -0
  98. package/dist/src/core/commands/status.js.map +1 -0
  99. package/dist/src/core/commands/validate.d.ts +47 -0
  100. package/dist/src/core/commands/validate.d.ts.map +1 -0
  101. package/dist/src/core/commands/validate.js +608 -0
  102. package/dist/src/core/commands/validate.js.map +1 -0
  103. package/dist/src/core/engine.d.ts +294 -0
  104. package/dist/src/core/engine.d.ts.map +1 -0
  105. package/dist/src/core/engine.js +219 -0
  106. package/dist/src/core/engine.js.map +1 -0
  107. package/dist/src/core/index.d.ts +14 -0
  108. package/dist/src/core/index.d.ts.map +1 -0
  109. package/dist/src/core/index.js +18 -0
  110. package/dist/src/core/index.js.map +1 -0
  111. package/dist/src/core/prompts/context-helpers.d.ts +48 -0
  112. package/dist/src/core/prompts/context-helpers.d.ts.map +1 -0
  113. package/dist/src/core/prompts/context-helpers.js +206 -0
  114. package/dist/src/core/prompts/context-helpers.js.map +1 -0
  115. package/dist/src/core/prompts/discovery.d.ts +11 -0
  116. package/dist/src/core/prompts/discovery.d.ts.map +1 -0
  117. package/dist/src/core/prompts/discovery.js +179 -0
  118. package/dist/src/core/prompts/discovery.js.map +1 -0
  119. package/dist/src/core/prompts/feedback.d.ts +38 -0
  120. package/dist/src/core/prompts/feedback.d.ts.map +1 -0
  121. package/dist/src/core/prompts/feedback.js +292 -0
  122. package/dist/src/core/prompts/feedback.js.map +1 -0
  123. package/dist/src/core/prompts/index.d.ts +12 -0
  124. package/dist/src/core/prompts/index.d.ts.map +1 -0
  125. package/dist/src/core/prompts/index.js +12 -0
  126. package/dist/src/core/prompts/index.js.map +1 -0
  127. package/dist/src/core/prompts/planning.d.ts +15 -0
  128. package/dist/src/core/prompts/planning.d.ts.map +1 -0
  129. package/dist/src/core/prompts/planning.js +293 -0
  130. package/dist/src/core/prompts/planning.js.map +1 -0
  131. package/dist/src/core/prompts/status.d.ts +41 -0
  132. package/dist/src/core/prompts/status.d.ts.map +1 -0
  133. package/dist/src/core/prompts/status.js +270 -0
  134. package/dist/src/core/prompts/status.js.map +1 -0
  135. package/dist/src/core/prompts/validate.d.ts +62 -0
  136. package/dist/src/core/prompts/validate.d.ts.map +1 -0
  137. package/dist/src/core/prompts/validate.js +302 -0
  138. package/dist/src/core/prompts/validate.js.map +1 -0
  139. package/dist/src/core/schemas.d.ts +5045 -0
  140. package/dist/src/core/schemas.d.ts.map +1 -0
  141. package/dist/src/core/schemas.js +492 -0
  142. package/dist/src/core/schemas.js.map +1 -0
  143. package/dist/src/core/state.d.ts +156 -0
  144. package/dist/src/core/state.d.ts.map +1 -0
  145. package/dist/src/core/state.js +608 -0
  146. package/dist/src/core/state.js.map +1 -0
  147. package/dist/src/core/types.d.ts +167 -0
  148. package/dist/src/core/types.d.ts.map +1 -0
  149. package/dist/src/core/types.js +102 -0
  150. package/dist/src/core/types.js.map +1 -0
  151. package/dist/src/core/utils.d.ts +39 -0
  152. package/dist/src/core/utils.d.ts.map +1 -0
  153. package/dist/src/core/utils.js +208 -0
  154. package/dist/src/core/utils.js.map +1 -0
  155. package/package.json +77 -0
@@ -0,0 +1,687 @@
1
+ /**
2
+ * Discovery Commands for Ralph Engine
3
+ *
4
+ * Handles project initialization and discovery conversation flow.
5
+ */
6
+ import { success, error, section, } from "../types.js";
7
+ import { now, validateMilestoneId, } from "../utils.js";
8
+ import { getDiscoveryGuidance, getElementDiscoveryGuidance } from "../prompts/discovery.js";
9
+ const DISCOVERY_GUIDANCE_DATA = [
10
+ {
11
+ category: "problem",
12
+ purpose: "Understand what we're solving and for whom",
13
+ starterQuestions: [
14
+ "What problem are we solving?",
15
+ "Who experiences this problem most acutely?",
16
+ "What happens if we don't solve this?",
17
+ ],
18
+ depthHints: {
19
+ quick: "Get a clear one-sentence problem statement",
20
+ standard: "Understand the problem, who it affects, and current workarounds",
21
+ deep: "Map the problem landscape, quantify impact, explore root causes",
22
+ },
23
+ includedIn: ["quick", "standard", "deep"],
24
+ },
25
+ {
26
+ category: "vision",
27
+ purpose: "Define what success looks like",
28
+ starterQuestions: [
29
+ "What does the ideal end state look like?",
30
+ "How will users' lives be different?",
31
+ ],
32
+ depthHints: {
33
+ quick: "Get a clear vision statement",
34
+ standard: "Understand the desired outcome and key benefits",
35
+ deep: "Explore alternative visions, trade-offs, and long-term implications",
36
+ },
37
+ includedIn: ["quick", "standard", "deep"],
38
+ },
39
+ {
40
+ category: "users",
41
+ purpose: "Understand who we're building for",
42
+ starterQuestions: [
43
+ "Who are the primary users?",
44
+ "What are their key pain points today?",
45
+ "How do they currently solve this problem?",
46
+ ],
47
+ depthHints: {
48
+ quick: "Identify the primary user group",
49
+ standard: "Understand 2-3 user types and their needs",
50
+ deep: "Build detailed personas, map user journeys, identify edge cases",
51
+ },
52
+ includedIn: ["standard", "deep"],
53
+ },
54
+ {
55
+ category: "values",
56
+ purpose: "Prioritize what matters most",
57
+ starterQuestions: [
58
+ "What's the most important quality this solution must have?",
59
+ "What would you sacrifice to get that quality?",
60
+ "What's the second most important quality?",
61
+ ],
62
+ depthHints: {
63
+ quick: "Identify the #1 priority",
64
+ standard: "Understand top 2-3 priorities and their trade-offs",
65
+ deep: "Build a complete value hierarchy with explicit trade-off decisions",
66
+ },
67
+ includedIn: ["standard", "deep"],
68
+ },
69
+ {
70
+ category: "constraints",
71
+ purpose: "Identify tech stack, team capabilities, and hard limits",
72
+ starterQuestions: [
73
+ "What's your current tech stack? What technologies are you comfortable with?",
74
+ "What skills does your team have? Any gaps?",
75
+ "What's your team size and availability?",
76
+ "Are there timeline or budget constraints?",
77
+ "Is there anything we absolutely cannot change?",
78
+ ],
79
+ depthHints: {
80
+ quick: "Note tech stack and team size",
81
+ standard: "Document tech stack, team skills, resources, and constraints",
82
+ deep: "Full capability assessment: tech, team, timeline, budget, dependencies",
83
+ },
84
+ includedIn: ["quick", "standard", "deep"],
85
+ },
86
+ {
87
+ category: "success",
88
+ purpose: "Define measurable outcomes",
89
+ starterQuestions: [
90
+ "How will we know if this is successful?",
91
+ "What metrics matter most?",
92
+ "What's the minimum viable success?",
93
+ ],
94
+ depthHints: {
95
+ quick: "Get one clear success metric",
96
+ standard: "Define 2-3 success criteria with targets",
97
+ deep: "Build a measurement framework with leading/lagging indicators",
98
+ },
99
+ includedIn: ["quick", "standard", "deep"],
100
+ },
101
+ {
102
+ category: "other",
103
+ purpose: "Research existing solutions and benchmarks",
104
+ starterQuestions: [
105
+ "What existing solutions have you looked at?",
106
+ "What do competitors do well? What do they do poorly?",
107
+ "Are there industry standards or benchmarks we should meet?",
108
+ ],
109
+ depthHints: {
110
+ quick: "Note any obvious references",
111
+ standard: "Review 2-3 alternatives and key differentiators",
112
+ deep: "Comprehensive competitive analysis with feature matrix",
113
+ },
114
+ includedIn: ["standard", "deep"],
115
+ },
116
+ ];
117
+ // Legacy questions for backward compatibility
118
+ const DISCOVERY_QUESTIONS = [
119
+ { id: "problem", category: "problem", question: "What problem are we solving?", required: true },
120
+ { id: "vision", category: "vision", question: "What does success look like?", required: true },
121
+ { id: "users", category: "users", question: "Who are the primary users?", required: false },
122
+ { id: "value1", category: "values", question: "What is the most important quality?", required: false },
123
+ { id: "success1", category: "success", question: "How will we know if this is successful?", required: true },
124
+ ];
125
+ // ============================================================================
126
+ // Helper Functions
127
+ // ============================================================================
128
+ function checkCategoryHasContent(category, context, constraints) {
129
+ switch (category) {
130
+ case "problem":
131
+ return !!context?.problem;
132
+ case "vision":
133
+ return !!context?.vision;
134
+ case "users":
135
+ return false; // Stored in history
136
+ case "values":
137
+ return (context?.values && context.values.length > 0) || false;
138
+ case "constraints":
139
+ return (constraints?.constraints && constraints.constraints.length > 0) || false;
140
+ case "success":
141
+ return (context?.success_criteria && context.success_criteria.length > 0) || false;
142
+ default:
143
+ return false;
144
+ }
145
+ }
146
+ function findDiscoveryGaps(context, constraints, depth) {
147
+ const gaps = [];
148
+ const guidanceForDepth = DISCOVERY_GUIDANCE_DATA.filter((g) => g.includedIn.includes(depth));
149
+ for (const guidance of guidanceForDepth) {
150
+ if (!checkCategoryHasContent(guidance.category, context, constraints)) {
151
+ gaps.push(guidance.category);
152
+ }
153
+ }
154
+ return gaps;
155
+ }
156
+ function createEmptyElementDiscovery(elementType, elementId, source) {
157
+ const timestamp = now();
158
+ return {
159
+ element_type: elementType,
160
+ element_id: elementId,
161
+ problem: undefined,
162
+ scope: [],
163
+ out_of_scope: [],
164
+ success_criteria: [],
165
+ constraints: [],
166
+ history: [],
167
+ status: "not_started",
168
+ source,
169
+ created: timestamp,
170
+ updated: timestamp,
171
+ };
172
+ }
173
+ async function updateContextFromEntry(state, entry) {
174
+ const context = (await state.readDiscoveryContext()) || {
175
+ values: [],
176
+ success_criteria: [],
177
+ };
178
+ const constraints = (await state.readConstraints()) || { constraints: [] };
179
+ switch (entry.category) {
180
+ case "problem":
181
+ context.problem = entry.answer;
182
+ context.gathered = now();
183
+ await state.writeDiscoveryContext(context);
184
+ break;
185
+ case "vision":
186
+ context.vision = entry.answer;
187
+ context.gathered = now();
188
+ await state.writeDiscoveryContext(context);
189
+ break;
190
+ case "values":
191
+ const valueId = `V${context.values.length + 1}`;
192
+ const value = {
193
+ id: valueId,
194
+ name: entry.answer.split(/[.!?]/)[0].slice(0, 50),
195
+ description: entry.answer,
196
+ priority: context.values.length + 1,
197
+ };
198
+ context.values.push(value);
199
+ context.gathered = now();
200
+ await state.writeDiscoveryContext(context);
201
+ break;
202
+ case "success":
203
+ context.success_criteria.push(entry.answer);
204
+ context.gathered = now();
205
+ await state.writeDiscoveryContext(context);
206
+ break;
207
+ case "constraints":
208
+ let constraintType = "business";
209
+ if (entry.question.toLowerCase().includes("technical")) {
210
+ constraintType = "technical";
211
+ }
212
+ else if (entry.question.toLowerCase().includes("resource")) {
213
+ constraintType = "resource";
214
+ }
215
+ else if (entry.question.toLowerCase().includes("cannot change") ||
216
+ entry.question.toLowerCase().includes("frozen")) {
217
+ constraintType = "frozen";
218
+ }
219
+ const constraint = {
220
+ id: `C${constraints.constraints.length + 1}`,
221
+ type: constraintType,
222
+ constraint: entry.answer,
223
+ impact: [],
224
+ };
225
+ constraints.constraints.push(constraint);
226
+ constraints.gathered = now();
227
+ await state.writeConstraints(constraints);
228
+ break;
229
+ }
230
+ }
231
+ // ============================================================================
232
+ // Init Command
233
+ // ============================================================================
234
+ export async function init(state, options) {
235
+ if (await state.isInitialized()) {
236
+ return error("Project already initialized. Use 'discover' to continue.", "ALREADY_INITIALIZED");
237
+ }
238
+ const projectName = options.name || "Untitled Project";
239
+ const project = await state.initialize(projectName);
240
+ const sections = [
241
+ section.header("Initializing Ralph"),
242
+ section.success(`Created .ralph/ directory`),
243
+ section.success(`Project: ${project.name}`),
244
+ section.success(`ID: ${project.id}`),
245
+ section.blank(),
246
+ section.info("Next step: Run discovery to gather project context"),
247
+ ];
248
+ const aiGuidance = {
249
+ situation: "Project initialized, ready to start discovery",
250
+ instructions: [
251
+ "Start a natural discovery conversation to gather project context",
252
+ "Ask about the problem being solved, vision, constraints, and success criteria",
253
+ "Keep it conversational - don't list upcoming steps or announce what you'll do",
254
+ ],
255
+ commands: [
256
+ {
257
+ command: "discover",
258
+ description: "Start or continue discovery conversation",
259
+ when: "To gather project context",
260
+ },
261
+ ],
262
+ };
263
+ return success({ project: { id: project.id, name: project.name } }, sections, aiGuidance);
264
+ }
265
+ // ============================================================================
266
+ // Status Command
267
+ // ============================================================================
268
+ export async function status(state) {
269
+ if (!(await state.isInitialized())) {
270
+ return error("No Ralph project found. Run 'discover init' first.", "NOT_INITIALIZED");
271
+ }
272
+ const context = await state.readDiscoveryContext();
273
+ const constraints = await state.readConstraints();
274
+ const history = await state.readDiscoveryHistory();
275
+ // Calculate completion
276
+ const answeredQuestions = new Set(history?.entries.map((e) => e.question) || []);
277
+ const requiredQuestions = DISCOVERY_QUESTIONS.filter((q) => q.required);
278
+ const answeredRequired = requiredQuestions.filter((q) => answeredQuestions.has(q.question)).length;
279
+ const progress = {
280
+ answeredRequired,
281
+ totalRequired: requiredQuestions.length,
282
+ percent: requiredQuestions.length > 0
283
+ ? Math.round((answeredRequired / requiredQuestions.length) * 100)
284
+ : 0,
285
+ };
286
+ const data = {
287
+ context,
288
+ constraints,
289
+ history,
290
+ progress,
291
+ isComplete: history?.is_complete || false,
292
+ };
293
+ const sections = [
294
+ section.header("Discovery Status"),
295
+ section.subheader("Progress"),
296
+ section.progress(progress.answeredRequired, progress.totalRequired, "Required questions"),
297
+ section.blank(),
298
+ section.subheader("Context Gathered"),
299
+ ];
300
+ if (context?.problem) {
301
+ sections.push(section.success(`Problem: ${context.problem.slice(0, 60)}...`));
302
+ }
303
+ else {
304
+ sections.push(section.dim(" Problem: Not yet defined"));
305
+ }
306
+ if (context?.vision) {
307
+ sections.push(section.success(`Vision: ${context.vision.slice(0, 60)}...`));
308
+ }
309
+ else {
310
+ sections.push(section.dim(" Vision: Not yet defined"));
311
+ }
312
+ if (context?.values && context.values.length > 0) {
313
+ sections.push(section.success(`Values: ${context.values.length} defined`));
314
+ }
315
+ else {
316
+ sections.push(section.dim(" Values: None defined"));
317
+ }
318
+ if (context?.success_criteria && context.success_criteria.length > 0) {
319
+ sections.push(section.success(`Success Criteria: ${context.success_criteria.length} defined`));
320
+ }
321
+ else {
322
+ sections.push(section.dim(" Success Criteria: None defined"));
323
+ }
324
+ sections.push(section.blank());
325
+ sections.push(section.subheader("Constraints"));
326
+ if (constraints?.constraints && constraints.constraints.length > 0) {
327
+ for (const c of constraints.constraints) {
328
+ sections.push(section.text(` [${c.type}] ${c.constraint}`));
329
+ }
330
+ }
331
+ else {
332
+ sections.push(section.dim(" No constraints defined"));
333
+ }
334
+ sections.push(section.blank());
335
+ if (history?.is_complete) {
336
+ sections.push(section.success("Discovery is COMPLETE"));
337
+ }
338
+ else {
339
+ sections.push(section.warning("Discovery is INCOMPLETE"));
340
+ }
341
+ return success(data, sections);
342
+ }
343
+ // ============================================================================
344
+ // Start Command (main discovery flow)
345
+ // ============================================================================
346
+ export async function start(state, options = {}) {
347
+ if (!(await state.isInitialized())) {
348
+ return error("No Ralph project found. Run 'discover init' first.", "NOT_INITIALIZED");
349
+ }
350
+ const history = await state.readDiscoveryHistory();
351
+ const context = await state.readDiscoveryContext();
352
+ const constraints = await state.readConstraints();
353
+ // Determine depth
354
+ const depth = options.depth || history?.depth_preference || "standard";
355
+ // Update stored preference if changed
356
+ if (options.depth && history && history.depth_preference !== options.depth) {
357
+ history.depth_preference = options.depth;
358
+ history.last_active = now();
359
+ await state.writeDiscoveryHistory(history);
360
+ }
361
+ const gaps = findDiscoveryGaps(context, constraints, depth);
362
+ const data = {
363
+ depth,
364
+ context,
365
+ constraints,
366
+ history,
367
+ gaps,
368
+ };
369
+ // Build sections
370
+ const depthLabels = {
371
+ quick: "Quick (essentials)",
372
+ standard: "Standard",
373
+ deep: "Deep (comprehensive)",
374
+ };
375
+ const sections = [
376
+ section.header("Discovery Conversation"),
377
+ section.blank(),
378
+ section.info(`Depth: ${depthLabels[depth]}`),
379
+ section.dim(" Change with: --quick, --standard, or --deep"),
380
+ section.blank(),
381
+ section.subheader("What We Know So Far"),
382
+ ];
383
+ const hasContext = context?.problem ||
384
+ context?.vision ||
385
+ (context?.values && context.values.length > 0) ||
386
+ (context?.success_criteria && context.success_criteria.length > 0) ||
387
+ (constraints?.constraints && constraints.constraints.length > 0);
388
+ if (!hasContext) {
389
+ sections.push(section.dim(" (Nothing gathered yet - let's start exploring!)"));
390
+ }
391
+ else {
392
+ if (context?.problem) {
393
+ sections.push(section.success(` Problem: ${context.problem.slice(0, 70)}${context.problem.length > 70 ? "..." : ""}`));
394
+ }
395
+ if (context?.vision) {
396
+ sections.push(section.success(` Vision: ${context.vision.slice(0, 70)}${context.vision.length > 70 ? "..." : ""}`));
397
+ }
398
+ if (context?.values && context.values.length > 0) {
399
+ sections.push(section.success(` Values: ${context.values.length} defined`));
400
+ }
401
+ if (context?.success_criteria && context.success_criteria.length > 0) {
402
+ sections.push(section.success(` Success criteria: ${context.success_criteria.length} defined`));
403
+ }
404
+ if (constraints?.constraints && constraints.constraints.length > 0) {
405
+ sections.push(section.success(` Constraints: ${constraints.constraints.length} defined`));
406
+ }
407
+ }
408
+ sections.push(section.blank());
409
+ sections.push(section.subheader("Suggested Areas to Explore"));
410
+ sections.push(section.blank());
411
+ const guidanceForDepth = DISCOVERY_GUIDANCE_DATA.filter((g) => g.includedIn.includes(depth));
412
+ for (const guidance of guidanceForDepth) {
413
+ const hasContent = checkCategoryHasContent(guidance.category, context, constraints);
414
+ const icon = hasContent ? "✓" : "○";
415
+ const statusText = hasContent ? "(has content)" : "(explore this)";
416
+ sections.push(section.text(` ${icon} [${guidance.category}] ${guidance.purpose} ${statusText}`));
417
+ sections.push(section.dim(` Goal: ${guidance.depthHints[depth]}`));
418
+ if (!hasContent) {
419
+ sections.push(section.dim(` Start with: "${guidance.starterQuestions[0]}"`));
420
+ }
421
+ }
422
+ sections.push(section.blank());
423
+ sections.push(section.divider());
424
+ sections.push(section.blank());
425
+ // Get AI guidance from prompts module
426
+ const aiGuidance = getDiscoveryGuidance(context, history, constraints, depth, gaps);
427
+ return success(data, sections, aiGuidance);
428
+ }
429
+ // ============================================================================
430
+ // Add Entry Command
431
+ // ============================================================================
432
+ export async function addEntry(state, options) {
433
+ if (!(await state.isInitialized())) {
434
+ return error("No Ralph project found.", "NOT_INITIALIZED");
435
+ }
436
+ // Read existing history
437
+ let history = await state.readDiscoveryHistory();
438
+ if (!history) {
439
+ history = { entries: [], is_complete: false };
440
+ }
441
+ // Add new entry
442
+ const entry = {
443
+ timestamp: now(),
444
+ question: options.question,
445
+ answer: options.answer,
446
+ category: options.category,
447
+ };
448
+ history.entries.push(entry);
449
+ if (!history.started) {
450
+ history.started = now();
451
+ }
452
+ history.last_active = now();
453
+ await state.writeDiscoveryHistory(history);
454
+ // Update context based on category
455
+ await updateContextFromEntry(state, entry);
456
+ const sections = [
457
+ section.success(`Added ${options.category} entry`),
458
+ ];
459
+ return success({ entry }, sections);
460
+ }
461
+ // ============================================================================
462
+ // Complete Command
463
+ // ============================================================================
464
+ export async function complete(state) {
465
+ if (!(await state.isInitialized())) {
466
+ return error("No Ralph project found.", "NOT_INITIALIZED");
467
+ }
468
+ const history = await state.readDiscoveryHistory();
469
+ if (!history) {
470
+ return error("No discovery history found.", "NO_HISTORY");
471
+ }
472
+ history.is_complete = true;
473
+ history.completed = now();
474
+ history.last_active = now();
475
+ await state.writeDiscoveryHistory(history);
476
+ const sections = [
477
+ section.success("Discovery checkpoint saved!"),
478
+ section.blank(),
479
+ section.info("You can continue discovery anytime with: discover"),
480
+ section.info("Next step: Generate milestones with 'plan milestones'"),
481
+ ];
482
+ const aiGuidance = {
483
+ situation: "Discovery marked complete, ready for milestone planning",
484
+ instructions: [
485
+ "Discovery is complete but can be continued anytime",
486
+ "Next step is to generate milestones based on discovery context",
487
+ ],
488
+ commands: [
489
+ {
490
+ command: "plan milestones",
491
+ description: "Generate milestones from discovery",
492
+ when: "To create the project roadmap",
493
+ },
494
+ {
495
+ command: "discover",
496
+ description: "Continue adding discovery context",
497
+ when: "If more context is needed",
498
+ },
499
+ ],
500
+ };
501
+ return success({ completed: true }, sections, aiGuidance);
502
+ }
503
+ // ============================================================================
504
+ // Element Discovery Commands (Epic/Milestone)
505
+ // ============================================================================
506
+ async function findEpicByIdOrSlug(state, epicIdOrSlug) {
507
+ const epicDirs = await state.listEpicDirs();
508
+ for (const dir of epicDirs) {
509
+ const match = dir.match(/^(E\d+)-(.+)$/);
510
+ if (!match)
511
+ continue;
512
+ const [, epicId, slug] = match;
513
+ if (epicId === epicIdOrSlug || dir === epicIdOrSlug || dir.startsWith(epicIdOrSlug + "-")) {
514
+ const epic = await state.readEpic(epicId, slug);
515
+ if (epic) {
516
+ return { id: epicId, slug, epic };
517
+ }
518
+ }
519
+ }
520
+ return null;
521
+ }
522
+ async function findMilestoneById(state, milestoneId) {
523
+ const index = await state.readMilestoneIndex();
524
+ if (!index)
525
+ return null;
526
+ return index.milestones.find((m) => m.id === milestoneId) || null;
527
+ }
528
+ export async function element(state, options) {
529
+ if (!(await state.isInitialized())) {
530
+ return error("No Ralph project found.", "NOT_INITIALIZED");
531
+ }
532
+ const { elementType, elementId } = options;
533
+ if (elementType === "epic") {
534
+ const epicInfo = await findEpicByIdOrSlug(state, elementId);
535
+ if (!epicInfo) {
536
+ return error(`Epic not found: ${elementId}`, "EPIC_NOT_FOUND");
537
+ }
538
+ let discovery = await state.readEpicDiscovery(epicInfo.id, epicInfo.slug);
539
+ if (!discovery) {
540
+ discovery = createEmptyElementDiscovery("epic", epicInfo.id, "user_added");
541
+ }
542
+ if (discovery.status !== "complete") {
543
+ discovery.status = "in_progress";
544
+ await state.writeEpicDiscovery(epicInfo.id, epicInfo.slug, discovery);
545
+ }
546
+ const sections = [
547
+ section.header(`Epic Discovery: ${epicInfo.epic.name}`),
548
+ section.info(`Epic: ${epicInfo.id} - ${epicInfo.epic.name}`),
549
+ section.dim(`Description: ${epicInfo.epic.description}`),
550
+ section.blank(),
551
+ ];
552
+ if (discovery.status === "complete") {
553
+ sections.push(section.success("Discovery for this epic is complete"));
554
+ }
555
+ const aiGuidance = getElementDiscoveryGuidance("epic", epicInfo.epic.name, discovery);
556
+ return success({ discovery, element: epicInfo.epic }, sections, aiGuidance);
557
+ }
558
+ else {
559
+ if (!validateMilestoneId(elementId)) {
560
+ return error(`Invalid milestone ID: ${elementId}. Expected format: M1, M2, etc.`, "INVALID_ID");
561
+ }
562
+ const milestone = await findMilestoneById(state, elementId);
563
+ if (!milestone) {
564
+ return error(`Milestone not found: ${elementId}`, "MILESTONE_NOT_FOUND");
565
+ }
566
+ let discovery = await state.readMilestoneDiscovery(elementId);
567
+ if (!discovery) {
568
+ discovery = createEmptyElementDiscovery("milestone", elementId, "user_added");
569
+ }
570
+ if (discovery.status !== "complete") {
571
+ discovery.status = "in_progress";
572
+ await state.writeMilestoneDiscovery(elementId, discovery);
573
+ }
574
+ const sections = [
575
+ section.header(`Milestone Discovery: ${milestone.name}`),
576
+ section.info(`Milestone: ${elementId} - ${milestone.name}`),
577
+ section.dim(`Description: ${milestone.description}`),
578
+ section.blank(),
579
+ ];
580
+ if (discovery.status === "complete") {
581
+ sections.push(section.success("Discovery for this milestone is complete"));
582
+ }
583
+ const aiGuidance = getElementDiscoveryGuidance("milestone", milestone.name, discovery);
584
+ return success({ discovery, element: milestone }, sections, aiGuidance);
585
+ }
586
+ }
587
+ export async function addElementEntry(state, options) {
588
+ if (!(await state.isInitialized())) {
589
+ return error("No Ralph project found.", "NOT_INITIALIZED");
590
+ }
591
+ const entry = {
592
+ timestamp: now(),
593
+ question: options.question,
594
+ answer: options.answer,
595
+ category: options.category,
596
+ };
597
+ if (options.elementType === "epic") {
598
+ const epicInfo = await findEpicByIdOrSlug(state, options.elementId);
599
+ if (!epicInfo) {
600
+ return error(`Epic not found: ${options.elementId}`, "EPIC_NOT_FOUND");
601
+ }
602
+ let discovery = await state.readEpicDiscovery(epicInfo.id, epicInfo.slug);
603
+ if (!discovery) {
604
+ discovery = createEmptyElementDiscovery("epic", epicInfo.id, "user_added");
605
+ }
606
+ discovery.history.push(entry);
607
+ updateElementDiscoveryFromEntry(discovery, entry, options.question);
608
+ await state.writeEpicDiscovery(epicInfo.id, epicInfo.slug, discovery);
609
+ }
610
+ else {
611
+ if (!validateMilestoneId(options.elementId)) {
612
+ return error(`Invalid milestone ID: ${options.elementId}`, "INVALID_ID");
613
+ }
614
+ let discovery = await state.readMilestoneDiscovery(options.elementId);
615
+ if (!discovery) {
616
+ discovery = createEmptyElementDiscovery("milestone", options.elementId, "user_added");
617
+ }
618
+ discovery.history.push(entry);
619
+ updateElementDiscoveryFromEntry(discovery, entry, options.question);
620
+ await state.writeMilestoneDiscovery(options.elementId, discovery);
621
+ }
622
+ const sections = [
623
+ section.success(`Added ${options.category} entry to ${options.elementType} ${options.elementId}`),
624
+ ];
625
+ return success({ entry }, sections);
626
+ }
627
+ function updateElementDiscoveryFromEntry(discovery, entry, question) {
628
+ const lowerQuestion = question.toLowerCase();
629
+ if (entry.category === "problem" || lowerQuestion.includes("problem")) {
630
+ discovery.problem = entry.answer;
631
+ }
632
+ else if (lowerQuestion.includes("in scope") && !lowerQuestion.includes("out")) {
633
+ discovery.scope.push(entry.answer);
634
+ }
635
+ else if (lowerQuestion.includes("out of scope")) {
636
+ discovery.out_of_scope.push(entry.answer);
637
+ }
638
+ else if (entry.category === "success" ||
639
+ lowerQuestion.includes("success") ||
640
+ lowerQuestion.includes("done")) {
641
+ discovery.success_criteria.push(entry.answer);
642
+ }
643
+ else if (entry.category === "constraints" ||
644
+ lowerQuestion.includes("constraint") ||
645
+ lowerQuestion.includes("dependencies") ||
646
+ lowerQuestion.includes("blockers")) {
647
+ discovery.constraints.push({
648
+ id: `C${discovery.constraints.length + 1}`,
649
+ type: "technical",
650
+ constraint: entry.answer,
651
+ impact: [],
652
+ });
653
+ }
654
+ }
655
+ export async function completeElement(state, options) {
656
+ if (!(await state.isInitialized())) {
657
+ return error("No Ralph project found.", "NOT_INITIALIZED");
658
+ }
659
+ if (options.elementType === "epic") {
660
+ const epicInfo = await findEpicByIdOrSlug(state, options.elementId);
661
+ if (!epicInfo) {
662
+ return error(`Epic not found: ${options.elementId}`, "EPIC_NOT_FOUND");
663
+ }
664
+ const discovery = await state.readEpicDiscovery(epicInfo.id, epicInfo.slug);
665
+ if (!discovery) {
666
+ return error(`No discovery found for epic ${options.elementId}`, "NO_DISCOVERY");
667
+ }
668
+ discovery.status = "complete";
669
+ await state.writeEpicDiscovery(epicInfo.id, epicInfo.slug, discovery);
670
+ }
671
+ else {
672
+ if (!validateMilestoneId(options.elementId)) {
673
+ return error(`Invalid milestone ID: ${options.elementId}`, "INVALID_ID");
674
+ }
675
+ const discovery = await state.readMilestoneDiscovery(options.elementId);
676
+ if (!discovery) {
677
+ return error(`No discovery found for milestone ${options.elementId}`, "NO_DISCOVERY");
678
+ }
679
+ discovery.status = "complete";
680
+ await state.writeMilestoneDiscovery(options.elementId, discovery);
681
+ }
682
+ const sections = [
683
+ section.success(`Discovery for ${options.elementType} ${options.elementId} marked as complete`),
684
+ ];
685
+ return success({ completed: true }, sections);
686
+ }
687
+ //# sourceMappingURL=discover.js.map