@shahmarasy/prodo 0.1.4 → 0.1.5

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 (173) hide show
  1. package/README.md +201 -97
  2. package/bin/prodo.cjs +6 -6
  3. package/dist/agents/agent-registry.d.ts +13 -0
  4. package/dist/agents/agent-registry.js +79 -0
  5. package/dist/agents/anthropic/index.d.ts +9 -0
  6. package/dist/agents/anthropic/index.js +55 -0
  7. package/dist/agents/base.d.ts +25 -0
  8. package/dist/agents/base.js +71 -0
  9. package/dist/agents/google/index.d.ts +9 -0
  10. package/dist/agents/google/index.js +53 -0
  11. package/dist/agents/mock/index.d.ts +11 -0
  12. package/dist/agents/mock/index.js +26 -0
  13. package/dist/agents/openai/index.d.ts +9 -0
  14. package/dist/agents/openai/index.js +57 -0
  15. package/dist/agents/system-prompts.d.ts +3 -0
  16. package/dist/agents/system-prompts.js +32 -0
  17. package/dist/cli/agent-command-installer.d.ts +4 -0
  18. package/dist/cli/agent-command-installer.js +148 -0
  19. package/dist/cli/agent-ids.d.ts +15 -0
  20. package/dist/cli/agent-ids.js +49 -0
  21. package/dist/cli/doctor.d.ts +1 -0
  22. package/dist/cli/doctor.js +144 -0
  23. package/dist/cli/fix-tui.d.ts +4 -0
  24. package/dist/cli/fix-tui.js +79 -0
  25. package/dist/cli/index.d.ts +9 -0
  26. package/dist/cli/index.js +465 -0
  27. package/dist/cli/init-tui.d.ts +23 -0
  28. package/dist/cli/init-tui.js +176 -0
  29. package/dist/cli/init.d.ts +11 -0
  30. package/dist/cli/init.js +334 -0
  31. package/dist/cli/normalize-interactive.d.ts +8 -0
  32. package/dist/cli/normalize-interactive.js +167 -0
  33. package/dist/cli/preset-loader.d.ts +4 -0
  34. package/dist/cli/preset-loader.js +210 -0
  35. package/dist/core/artifact-registry.d.ts +11 -0
  36. package/dist/core/artifact-registry.js +49 -0
  37. package/dist/core/artifacts.d.ts +10 -0
  38. package/dist/core/artifacts.js +892 -0
  39. package/dist/core/clean.d.ts +10 -0
  40. package/dist/core/clean.js +74 -0
  41. package/dist/core/consistency.d.ts +8 -0
  42. package/dist/core/consistency.js +328 -0
  43. package/dist/core/constants.d.ts +7 -0
  44. package/dist/core/constants.js +64 -0
  45. package/dist/core/errors.d.ts +3 -0
  46. package/dist/core/errors.js +10 -0
  47. package/dist/core/fix.d.ts +31 -0
  48. package/dist/core/fix.js +188 -0
  49. package/dist/core/hook-executor.d.ts +1 -0
  50. package/dist/core/hook-executor.js +175 -0
  51. package/dist/core/markdown.d.ts +16 -0
  52. package/dist/core/markdown.js +81 -0
  53. package/dist/core/normalize.d.ts +8 -0
  54. package/dist/core/normalize.js +125 -0
  55. package/dist/core/normalized-brief.d.ts +48 -0
  56. package/dist/core/normalized-brief.js +182 -0
  57. package/dist/core/output-index.d.ts +13 -0
  58. package/dist/core/output-index.js +55 -0
  59. package/dist/core/paths.d.ts +17 -0
  60. package/dist/core/paths.js +80 -0
  61. package/dist/core/project-config.d.ts +14 -0
  62. package/dist/core/project-config.js +69 -0
  63. package/dist/core/registry.d.ts +13 -0
  64. package/dist/core/registry.js +115 -0
  65. package/dist/core/settings.d.ts +7 -0
  66. package/dist/core/settings.js +35 -0
  67. package/dist/core/template-engine.d.ts +3 -0
  68. package/dist/core/template-engine.js +43 -0
  69. package/dist/core/template-resolver.d.ts +15 -0
  70. package/dist/core/template-resolver.js +46 -0
  71. package/dist/core/templates.d.ts +33 -0
  72. package/dist/core/templates.js +440 -0
  73. package/dist/core/terminology.d.ts +21 -0
  74. package/dist/core/terminology.js +143 -0
  75. package/dist/core/tracing.d.ts +21 -0
  76. package/dist/core/tracing.js +74 -0
  77. package/dist/core/types.d.ts +35 -0
  78. package/dist/core/types.js +5 -0
  79. package/dist/core/utils.d.ts +7 -0
  80. package/dist/core/utils.js +66 -0
  81. package/dist/core/validate.d.ts +10 -0
  82. package/dist/core/validate.js +226 -0
  83. package/dist/core/validator.d.ts +5 -0
  84. package/dist/core/validator.js +76 -0
  85. package/dist/core/version.d.ts +1 -0
  86. package/dist/core/version.js +30 -0
  87. package/dist/core/workflow-commands.d.ts +7 -0
  88. package/dist/core/workflow-commands.js +29 -0
  89. package/dist/i18n/en.json +45 -0
  90. package/dist/i18n/index.d.ts +5 -0
  91. package/dist/i18n/index.js +63 -0
  92. package/dist/i18n/tr.json +45 -0
  93. package/dist/providers/index.d.ts +2 -1
  94. package/dist/providers/index.js +20 -6
  95. package/dist/providers/mock-provider.d.ts +1 -1
  96. package/dist/providers/mock-provider.js +7 -6
  97. package/dist/providers/openai-provider.d.ts +1 -1
  98. package/dist/providers/openai-provider.js +1 -1
  99. package/dist/skills/engine.d.ts +10 -0
  100. package/dist/skills/engine.js +75 -0
  101. package/dist/skills/fix-skill.d.ts +2 -0
  102. package/dist/skills/fix-skill.js +38 -0
  103. package/dist/skills/generate-artifact-skill.d.ts +2 -0
  104. package/dist/skills/generate-artifact-skill.js +32 -0
  105. package/dist/skills/generate-pipeline-skill.d.ts +2 -0
  106. package/dist/skills/generate-pipeline-skill.js +45 -0
  107. package/dist/skills/normalize-skill.d.ts +2 -0
  108. package/dist/skills/normalize-skill.js +29 -0
  109. package/dist/skills/types.d.ts +28 -0
  110. package/dist/skills/types.js +2 -0
  111. package/dist/skills/validate-skill.d.ts +2 -0
  112. package/dist/skills/validate-skill.js +29 -0
  113. package/package.json +74 -45
  114. package/src/agents/agent-registry.ts +93 -0
  115. package/src/agents/anthropic/index.ts +86 -0
  116. package/src/agents/anthropic/manifest.json +7 -0
  117. package/src/agents/base.ts +77 -0
  118. package/src/agents/google/index.ts +79 -0
  119. package/src/agents/google/manifest.json +7 -0
  120. package/src/agents/mock/index.ts +32 -0
  121. package/src/agents/mock/manifest.json +7 -0
  122. package/src/agents/openai/index.ts +83 -0
  123. package/src/agents/openai/manifest.json +7 -0
  124. package/src/agents/system-prompts.ts +35 -0
  125. package/src/{agent-command-installer.ts → cli/agent-command-installer.ts} +164 -164
  126. package/src/{agents.ts → cli/agent-ids.ts} +58 -58
  127. package/src/{doctor.ts → cli/doctor.ts} +157 -137
  128. package/src/cli/fix-tui.ts +111 -0
  129. package/src/{cli.ts → cli/index.ts} +459 -410
  130. package/src/{init-tui.ts → cli/init-tui.ts} +208 -208
  131. package/src/{init.ts → cli/init.ts} +398 -398
  132. package/src/cli/normalize-interactive.ts +241 -0
  133. package/src/{preset-loader.ts → cli/preset-loader.ts} +237 -237
  134. package/src/{artifact-registry.ts → core/artifact-registry.ts} +69 -69
  135. package/src/{artifacts.ts → core/artifacts.ts} +1081 -1072
  136. package/src/core/clean.ts +88 -0
  137. package/src/{consistency.ts → core/consistency.ts} +374 -303
  138. package/src/{constants.ts → core/constants.ts} +72 -72
  139. package/src/{errors.ts → core/errors.ts} +7 -7
  140. package/src/core/fix.ts +253 -0
  141. package/src/{hook-executor.ts → core/hook-executor.ts} +196 -196
  142. package/src/{markdown.ts → core/markdown.ts} +93 -73
  143. package/src/{normalize.ts → core/normalize.ts} +145 -137
  144. package/src/{normalized-brief.ts → core/normalized-brief.ts} +227 -206
  145. package/src/{output-index.ts → core/output-index.ts} +59 -59
  146. package/src/{paths.ts → core/paths.ts} +75 -71
  147. package/src/{project-config.ts → core/project-config.ts} +78 -78
  148. package/src/{registry.ts → core/registry.ts} +119 -119
  149. package/src/{settings.ts → core/settings.ts} +35 -35
  150. package/src/core/template-engine.ts +45 -0
  151. package/src/{template-resolver.ts → core/template-resolver.ts} +54 -54
  152. package/src/{templates.ts → core/templates.ts} +452 -452
  153. package/src/core/terminology.ts +177 -0
  154. package/src/core/tracing.ts +110 -0
  155. package/src/{types.ts → core/types.ts} +46 -46
  156. package/src/{utils.ts → core/utils.ts} +64 -64
  157. package/src/{validate.ts → core/validate.ts} +252 -246
  158. package/src/{validator.ts → core/validator.ts} +92 -92
  159. package/src/{version.ts → core/version.ts} +24 -24
  160. package/src/{workflow-commands.ts → core/workflow-commands.ts} +32 -32
  161. package/src/i18n/en.json +45 -0
  162. package/src/i18n/index.ts +58 -0
  163. package/src/i18n/tr.json +45 -0
  164. package/src/providers/index.ts +29 -12
  165. package/src/providers/mock-provider.ts +200 -199
  166. package/src/providers/openai-provider.ts +88 -88
  167. package/src/skills/engine.ts +94 -0
  168. package/src/skills/fix-skill.ts +38 -0
  169. package/src/skills/generate-artifact-skill.ts +32 -0
  170. package/src/skills/generate-pipeline-skill.ts +49 -0
  171. package/src/skills/normalize-skill.ts +29 -0
  172. package/src/skills/types.ts +36 -0
  173. package/src/skills/validate-skill.ts +29 -0
@@ -0,0 +1,241 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import {
4
+ checkConfidence,
5
+ parseNormalizedBriefOrThrow,
6
+ type ConfidenceCheckResult,
7
+ type NormalizedBrief
8
+ } from "../core/normalized-brief";
9
+ import { runNormalize } from "../core/normalize";
10
+ import { normalizedBriefPath, normHistoryPath, prodoPath } from "../core/paths";
11
+ import { fileExists, readJsonFile } from "../core/utils";
12
+ import { UserError } from "../core/errors";
13
+
14
+ const CONFIDENCE_FIELDS = [
15
+ "product_name",
16
+ "problem",
17
+ "audience",
18
+ "goals",
19
+ "core_features"
20
+ ] as const;
21
+
22
+ const FIELD_QUESTIONS: Record<string, (current: unknown) => string> = {
23
+ product_name: (val) =>
24
+ `The product name was detected as "${val ?? "(empty)"}". Can you confirm or provide the exact product name?`,
25
+ problem: (val) =>
26
+ val
27
+ ? `The problem statement seems unclear: "${String(val).slice(0, 80)}...". Can you describe the core problem more precisely?`
28
+ : "What is the core problem this product solves?",
29
+ audience: (val) =>
30
+ Array.isArray(val) && val.length > 0
31
+ ? `The target audience was detected as: ${(val as string[]).join(", ")}. Is this correct? Add any missing groups.`
32
+ : "Who is the target audience for this product?",
33
+ goals: (val) =>
34
+ Array.isArray(val) && val.length > 0
35
+ ? `The following goals were identified: ${(val as string[]).join("; ")}. Are these correct? Add any missing goals.`
36
+ : "What are the primary goals of this product?",
37
+ core_features: (val) =>
38
+ Array.isArray(val) && val.length > 0
39
+ ? `These core features were detected: ${(val as string[]).join("; ")}. Are these correct? Add any missing features.`
40
+ : "What are the core features of this product?"
41
+ };
42
+
43
+ type NormHistorySession = {
44
+ timestamp: string;
45
+ iteration: number;
46
+ questions: Array<{ field: string; question: string; confidence: number }>;
47
+ answers: Record<string, string>;
48
+ };
49
+
50
+ type NormHistory = {
51
+ version: string;
52
+ sessions: NormHistorySession[];
53
+ };
54
+
55
+ async function loadHistory(cwd: string): Promise<NormHistory> {
56
+ const file = normHistoryPath(cwd);
57
+ if (!(await fileExists(file))) {
58
+ return { version: "1.0", sessions: [] };
59
+ }
60
+ try {
61
+ return await readJsonFile<NormHistory>(file);
62
+ } catch {
63
+ return { version: "1.0", sessions: [] };
64
+ }
65
+ }
66
+
67
+ async function saveHistory(cwd: string, history: NormHistory): Promise<void> {
68
+ const file = normHistoryPath(cwd);
69
+ await fs.mkdir(path.dirname(file), { recursive: true });
70
+ await fs.writeFile(file, `${JSON.stringify(history, null, 2)}\n`, "utf8");
71
+ }
72
+
73
+ function buildQuestions(
74
+ brief: NormalizedBrief,
75
+ confidenceResult: ConfidenceCheckResult
76
+ ): Array<{ field: string; question: string; confidence: number }> {
77
+ return confidenceResult.lowFields.map(({ field, confidence }) => {
78
+ const current = brief[field as keyof NormalizedBrief];
79
+ const questionFn = FIELD_QUESTIONS[field];
80
+ const question = questionFn ? questionFn(current) : `Please clarify the "${field}" field.`;
81
+ return { field, question, confidence };
82
+ });
83
+ }
84
+
85
+ export type InteractiveNormalizeOptions = {
86
+ cwd: string;
87
+ brief?: string;
88
+ out?: string;
89
+ maxIterations?: number;
90
+ log?: (message: string) => void;
91
+ };
92
+
93
+ export async function runInteractiveNormalize(
94
+ options: InteractiveNormalizeOptions
95
+ ): Promise<string> {
96
+ const { cwd, maxIterations = 3, log = console.log } = options;
97
+ const isTTY = process.stdout.isTTY === true;
98
+
99
+ if (!isTTY) {
100
+ log("Non-interactive terminal detected. Running standard normalize instead.");
101
+ return runNormalize({ cwd, brief: options.brief, out: options.out });
102
+ }
103
+
104
+ const clack = await loadClack();
105
+ if (!clack) {
106
+ log("Interactive prompts not available. Running standard normalize.");
107
+ return runNormalize({ cwd, brief: options.brief, out: options.out });
108
+ }
109
+
110
+ const history = await loadHistory(cwd);
111
+ let additionalContext: Record<string, string> = {};
112
+ let iteration = 0;
113
+
114
+ clack.intro("Prodo Interactive Normalize");
115
+
116
+ while (iteration < maxIterations) {
117
+ iteration += 1;
118
+ log(`\nNormalize iteration ${iteration}/${maxIterations}...`);
119
+
120
+ const outPath = await runNormalizeRelaxed({
121
+ cwd,
122
+ brief: options.brief,
123
+ out: options.out,
124
+ additionalContext
125
+ });
126
+
127
+ const normalizedRaw = JSON.parse(
128
+ await fs.readFile(outPath, "utf8")
129
+ ) as Record<string, unknown>;
130
+ const brief = parseNormalizedBriefOrThrow(normalizedRaw);
131
+
132
+ const result = checkConfidence(brief, [...CONFIDENCE_FIELDS], 0.7);
133
+
134
+ if (result.pass) {
135
+ log("All fields have sufficient confidence.");
136
+ clack.outro("Normalization complete!");
137
+ await saveHistory(cwd, history);
138
+ return outPath;
139
+ }
140
+
141
+ const questions = buildQuestions(brief, result);
142
+ log(`\n${questions.length} field(s) need clarification:\n`);
143
+
144
+ const session: NormHistorySession = {
145
+ timestamp: new Date().toISOString(),
146
+ iteration,
147
+ questions,
148
+ answers: {}
149
+ };
150
+
151
+ for (const q of questions) {
152
+ const answer = await clack.text({
153
+ message: `[${(q.confidence * 100).toFixed(0)}% confidence] ${q.question}`,
154
+ placeholder: "Type your answer or press Enter to skip..."
155
+ });
156
+
157
+ if (clack.isCancel(answer)) {
158
+ clack.cancel("Normalize cancelled.");
159
+ await saveHistory(cwd, history);
160
+ return outPath;
161
+ }
162
+
163
+ const answerStr = typeof answer === "string" ? answer.trim() : "";
164
+ if (answerStr.length > 0) {
165
+ additionalContext[q.field] = answerStr;
166
+ session.answers[q.field] = answerStr;
167
+ }
168
+ }
169
+
170
+ history.sessions.push(session);
171
+
172
+ if (Object.keys(session.answers).length === 0) {
173
+ log("No clarifications provided. Keeping current normalization.");
174
+ clack.outro("Normalization complete (as-is).");
175
+ await saveHistory(cwd, history);
176
+ return outPath;
177
+ }
178
+
179
+ const shouldContinue = await clack.confirm({
180
+ message: "Continue refining? (No = accept current result)"
181
+ });
182
+
183
+ if (clack.isCancel(shouldContinue) || shouldContinue === false) {
184
+ clack.outro("Normalization accepted.");
185
+ await saveHistory(cwd, history);
186
+ return outPath;
187
+ }
188
+ }
189
+
190
+ log(`Maximum iterations (${maxIterations}) reached.`);
191
+ clack.outro("Normalization complete.");
192
+ await saveHistory(cwd, history);
193
+
194
+ const finalPath = options.out
195
+ ? path.resolve(cwd, options.out)
196
+ : normalizedBriefPath(cwd);
197
+ return finalPath;
198
+ }
199
+
200
+ async function runNormalizeRelaxed(options: {
201
+ cwd: string;
202
+ brief?: string;
203
+ out?: string;
204
+ additionalContext?: Record<string, string>;
205
+ }): Promise<string> {
206
+ try {
207
+ return await runNormalize(options);
208
+ } catch (error) {
209
+ if (
210
+ error instanceof UserError &&
211
+ error.message.includes("Normalization confidence too low")
212
+ ) {
213
+ const outPath = options.out
214
+ ? path.resolve(options.cwd, options.out)
215
+ : normalizedBriefPath(options.cwd);
216
+ if (await fileExists(outPath)) return outPath;
217
+ }
218
+ throw error;
219
+ }
220
+ }
221
+
222
+ type ClackModule = {
223
+ intro: (title: string) => void;
224
+ outro: (message: string) => void;
225
+ cancel: (message: string) => void;
226
+ text: (opts: { message: string; placeholder?: string }) => Promise<string | symbol>;
227
+ confirm: (opts: { message: string }) => Promise<boolean | symbol>;
228
+ isCancel: (value: unknown) => boolean;
229
+ };
230
+
231
+ const dynamicImport = new Function("specifier", "return import(specifier)") as (
232
+ specifier: string
233
+ ) => Promise<unknown>;
234
+
235
+ async function loadClack(): Promise<ClackModule | null> {
236
+ try {
237
+ return (await dynamicImport("@clack/prompts")) as ClackModule;
238
+ } catch {
239
+ return null;
240
+ }
241
+ }