@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
@@ -1,137 +1,157 @@
1
- import path from "node:path";
2
- import { spawn } from "node:child_process";
3
- import { prodoPath } from "./paths";
4
- import { fileExists } from "./utils";
5
- import { readCliVersion } from "./version";
6
-
7
- type RowStatus = "available" | "not_found" | "ide";
8
-
9
- type DoctorRow = {
10
- name: string;
11
- status: RowStatus;
12
- detail: string;
13
- };
14
-
15
- function termWidth(): number {
16
- return Math.max(80, process.stdout.columns ?? 100);
17
- }
18
-
19
- function stripAnsi(input: string): string {
20
- return input.replace(/\u001B\[[0-9;]*m/g, "");
21
- }
22
-
23
- function color(input: string, code: string): string {
24
- if (!process.stdout.isTTY) return input;
25
- return `${code}${input}\u001B[0m`;
26
- }
27
-
28
- function center(input: string, width: number): string {
29
- const visible = stripAnsi(input).length;
30
- const left = Math.max(0, Math.floor((width - visible) / 2));
31
- return `${" ".repeat(left)}${input}`;
32
- }
33
-
34
- function iconFor(status: RowStatus): string {
35
- if (status === "available") return color("✔", "\u001B[32m");
36
- if (status === "not_found") return color("", "\u001B[31m");
37
- return color("", "\u001B[33m");
38
- }
39
-
40
- function labelFor(status: RowStatus): string {
41
- if (status === "available") return color("available", "\u001B[32m");
42
- if (status === "not_found") return color("not found", "\u001B[31m");
43
- return color("IDE-based", "\u001B[2;33m");
44
- }
45
-
46
- function renderRows(rows: DoctorRow[]): string[] {
47
- const leftWidth = Math.max(...rows.map((row) => row.name.length), 10);
48
- return rows.map((row) => {
49
- const left = row.name.padEnd(leftWidth, " ");
50
- const status = `${labelFor(row.status)}${row.detail ? ` (${row.detail})` : ""}`;
51
- return ` ${iconFor(row.status)} ${left} ${status}`;
52
- });
53
- }
54
-
55
- async function commandExists(command: string): Promise<boolean> {
56
- return new Promise((resolve) => {
57
- const lookup = process.platform === "win32" ? "where" : "which";
58
- const child = spawn(lookup, [command], { stdio: "ignore" });
59
- child.on("error", () => resolve(false));
60
- child.on("close", (code) => resolve(code === 0));
61
- });
62
- }
63
-
64
- async function firstAvailable(commands: string[]): Promise<boolean> {
65
- for (const command of commands) {
66
- if (await commandExists(command)) return true;
67
- }
68
- return false;
69
- }
70
-
71
- function renderLogo(width: number): string {
72
- const cyan = "\u001B[38;5;45m";
73
- const blue = "\u001B[38;5;39m";
74
- const logo = [
75
- "██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ",
76
- "██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██╔═══██╗",
77
- "██████╔╝██████╔╝██║ ██║██║ ██║██║ ██║",
78
- "██╔═══╝ ██╔══██╗██║ ██║██║ ██║██║ ██║",
79
- "██║ ██║ ██║╚██████╔╝██████╔╝╚██████╔╝"
80
- ];
81
- const painted = logo.map((line, idx) => color(line, idx % 2 === 0 ? cyan : blue));
82
- return painted.map((line) => center(line, width)).join("\n");
83
- }
84
-
85
- export async function runDoctor(cwd: string, out: (line: string) => void): Promise<void> {
86
- const width = termWidth();
87
- const version = await readCliVersion(cwd);
88
- const isInitialized = await fileExists(prodoPath(cwd));
89
-
90
- const codex = await firstAvailable(["codex"]);
91
- const gemini = await firstAvailable(["gemini", "gemini-cli"]);
92
- const claude = await firstAvailable(["claude", "claude-cli"]);
93
-
94
- const git = await firstAvailable(["git"]);
95
- const node = await firstAvailable(["node"]);
96
- const vscode = await firstAvailable(["code"]);
97
-
98
- const coreRows: DoctorRow[] = [
99
- { name: "Prodo CLI", status: "available", detail: `v${version}` },
100
- {
101
- name: "Project initialized",
102
- status: isInitialized ? "available" : "not_found",
103
- detail: isInitialized ? ".prodo found" : ".prodo missing"
104
- }
105
- ];
106
-
107
- const aiRows: DoctorRow[] = [
108
- { name: "Codex CLI", status: codex ? "available" : "not_found", detail: codex ? "available" : "not found" },
109
- { name: "Gemini CLI", status: gemini ? "available" : "not_found", detail: gemini ? "available" : "not found" },
110
- { name: "Claude CLI", status: claude ? "available" : "not_found", detail: claude ? "available" : "not found" }
111
- ];
112
-
113
- const devRows: DoctorRow[] = [
114
- { name: "Git", status: git ? "available" : "not_found", detail: git ? "available" : "not found" },
115
- { name: "Node.js", status: node ? "available" : "not_found", detail: node ? "available" : "not found" },
116
- { name: "Visual Studio Code", status: vscode ? "available" : "not_found", detail: vscode ? "available" : "not found" },
117
- { name: "Cursor", status: "ide", detail: "IDE-based, no CLI check" }
118
- ];
119
-
120
- out("");
121
- out(renderLogo(width));
122
- out("");
123
- out(center(color("Prodo — Product Artifact Toolkit", "\u001B[1;37m"), width));
124
- out(center(color("Crafted by Codex, guided by Shahmarasy intelligence", "\u001B[2;37m"), width));
125
- out("");
126
- out("Checking environment...");
127
- out("");
128
- out(color("Core", "\u001B[1m"));
129
- for (const line of renderRows(coreRows)) out(line);
130
- out("");
131
- out(color("AI / Agents", "\u001B[1m"));
132
- for (const line of renderRows(aiRows)) out(line);
133
- out("");
134
- out(color("Dev Tools", "\u001B[1m"));
135
- for (const line of renderRows(devRows)) out(line);
136
- out("");
137
- }
1
+ import path from "node:path";
2
+ import { spawn } from "node:child_process";
3
+ import { getGlobalRegistry } from "../agents/agent-registry";
4
+ import { prodoPath } from "../core/paths";
5
+ import { fileExists } from "../core/utils";
6
+ import { readCliVersion } from "../core/version";
7
+
8
+ type RowStatus = "available" | "not_found" | "ide";
9
+
10
+ type DoctorRow = {
11
+ name: string;
12
+ status: RowStatus;
13
+ detail: string;
14
+ };
15
+
16
+ function termWidth(): number {
17
+ return Math.max(80, process.stdout.columns ?? 100);
18
+ }
19
+
20
+ function stripAnsi(input: string): string {
21
+ return input.replace(/\u001B\[[0-9;]*m/g, "");
22
+ }
23
+
24
+ function color(input: string, code: string): string {
25
+ if (!process.stdout.isTTY) return input;
26
+ return `${code}${input}\u001B[0m`;
27
+ }
28
+
29
+ function center(input: string, width: number): string {
30
+ const visible = stripAnsi(input).length;
31
+ const left = Math.max(0, Math.floor((width - visible) / 2));
32
+ return `${" ".repeat(left)}${input}`;
33
+ }
34
+
35
+ function iconFor(status: RowStatus): string {
36
+ if (status === "available") return color("", "\u001B[32m");
37
+ if (status === "not_found") return color("", "\u001B[31m");
38
+ return color("•", "\u001B[33m");
39
+ }
40
+
41
+ function labelFor(status: RowStatus): string {
42
+ if (status === "available") return color("available", "\u001B[32m");
43
+ if (status === "not_found") return color("not found", "\u001B[31m");
44
+ return color("IDE-based", "\u001B[2;33m");
45
+ }
46
+
47
+ function renderRows(rows: DoctorRow[]): string[] {
48
+ const leftWidth = Math.max(...rows.map((row) => row.name.length), 10);
49
+ return rows.map((row) => {
50
+ const left = row.name.padEnd(leftWidth, " ");
51
+ const status = `${labelFor(row.status)}${row.detail ? ` (${row.detail})` : ""}`;
52
+ return ` ${iconFor(row.status)} ${left} ${status}`;
53
+ });
54
+ }
55
+
56
+ async function commandExists(command: string): Promise<boolean> {
57
+ return new Promise((resolve) => {
58
+ const lookup = process.platform === "win32" ? "where" : "which";
59
+ const child = spawn(lookup, [command], { stdio: "ignore" });
60
+ child.on("error", () => resolve(false));
61
+ child.on("close", (code) => resolve(code === 0));
62
+ });
63
+ }
64
+
65
+ async function firstAvailable(commands: string[]): Promise<boolean> {
66
+ for (const command of commands) {
67
+ if (await commandExists(command)) return true;
68
+ }
69
+ return false;
70
+ }
71
+
72
+ function renderLogo(width: number): string {
73
+ const cyan = "\u001B[38;5;45m";
74
+ const blue = "\u001B[38;5;39m";
75
+ const logo = [
76
+ "██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ",
77
+ "██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██╔═══██╗",
78
+ "██████╔╝██████╔╝██║ ██║██║ ██║██║ ██║",
79
+ "██╔═══╝ ██╔══██╗██║ ██║██║ ██║██║ ██║",
80
+ "██║ ██║ ██║╚██████╔╝██████╔╝╚██████╔╝"
81
+ ];
82
+ const painted = logo.map((line, idx) => color(line, idx % 2 === 0 ? cyan : blue));
83
+ return painted.map((line) => center(line, width)).join("\n");
84
+ }
85
+
86
+ export async function runDoctor(cwd: string, out: (line: string) => void): Promise<void> {
87
+ const width = termWidth();
88
+ const version = await readCliVersion(cwd);
89
+ const isInitialized = await fileExists(prodoPath(cwd));
90
+
91
+ const codex = await firstAvailable(["codex"]);
92
+ const gemini = await firstAvailable(["gemini", "gemini-cli"]);
93
+ const claude = await firstAvailable(["claude", "claude-cli"]);
94
+
95
+ const git = await firstAvailable(["git"]);
96
+ const node = await firstAvailable(["node"]);
97
+ const vscode = await firstAvailable(["code"]);
98
+
99
+ const coreRows: DoctorRow[] = [
100
+ { name: "Prodo CLI", status: "available", detail: `v${version}` },
101
+ {
102
+ name: "Project initialized",
103
+ status: isInitialized ? "available" : "not_found",
104
+ detail: isInitialized ? ".prodo found" : ".prodo missing"
105
+ }
106
+ ];
107
+
108
+ const aiCliRows: DoctorRow[] = [
109
+ { name: "Codex CLI", status: codex ? "available" : "not_found", detail: codex ? "available" : "not found" },
110
+ { name: "Gemini CLI", status: gemini ? "available" : "not_found", detail: gemini ? "available" : "not found" },
111
+ { name: "Claude CLI", status: claude ? "available" : "not_found", detail: claude ? "available" : "not found" }
112
+ ];
113
+
114
+ const registry = getGlobalRegistry();
115
+ const agentRows: DoctorRow[] = [];
116
+ for (const agent of registry.list()) {
117
+ if (agent.name === "mock") continue;
118
+ const available = await agent.isAvailable();
119
+ const config = agent.getConfig();
120
+ const sdkLabel = config.sdkRequired ? `SDK: ${config.sdkRequired}` : "";
121
+ const envLabel = config.envVars.filter((v) => !process.env[v]).map((v) => `${v} missing`).join(", ");
122
+ const detail = available ? "ready" : [sdkLabel, envLabel].filter(Boolean).join(", ") || "not configured";
123
+ agentRows.push({
124
+ name: config.displayName,
125
+ status: available ? "available" : "not_found",
126
+ detail
127
+ });
128
+ }
129
+
130
+ const devRows: DoctorRow[] = [
131
+ { name: "Git", status: git ? "available" : "not_found", detail: git ? "available" : "not found" },
132
+ { name: "Node.js", status: node ? "available" : "not_found", detail: node ? "available" : "not found" },
133
+ { name: "Visual Studio Code", status: vscode ? "available" : "not_found", detail: vscode ? "available" : "not found" },
134
+ { name: "Cursor", status: "ide", detail: "IDE-based, no CLI check" }
135
+ ];
136
+
137
+ out("");
138
+ out(renderLogo(width));
139
+ out("");
140
+ out(center(color("Prodo — Product Artifact Toolkit", "\u001B[1;37m"), width));
141
+ out(center(color("Crafted by Codex, guided by Shahmarasy intelligence", "\u001B[2;37m"), width));
142
+ out("");
143
+ out("Checking environment...");
144
+ out("");
145
+ out(color("Core", "\u001B[1m"));
146
+ for (const line of renderRows(coreRows)) out(line);
147
+ out("");
148
+ out(color("AI CLI Tools", "\u001B[1m"));
149
+ for (const line of renderRows(aiCliRows)) out(line);
150
+ out("");
151
+ out(color("LLM Agents (SDK)", "\u001B[1m"));
152
+ for (const line of renderRows(agentRows)) out(line);
153
+ out("");
154
+ out(color("Dev Tools", "\u001B[1m"));
155
+ for (const line of renderRows(devRows)) out(line);
156
+ out("");
157
+ }
@@ -0,0 +1,111 @@
1
+ import type { FixProposal, FixResult } from "../core/fix";
2
+
3
+ const dynamicImport = new Function("specifier", "return import(specifier)") as (
4
+ specifier: string
5
+ ) => Promise<unknown>;
6
+
7
+ type ClackModule = {
8
+ intro: (title: string) => void;
9
+ outro: (message: string) => void;
10
+ cancel: (message: string) => void;
11
+ confirm: (opts: { message: string }) => Promise<boolean | symbol>;
12
+ isCancel: (value: unknown) => boolean;
13
+ log: {
14
+ info: (message: string) => void;
15
+ warn: (message: string) => void;
16
+ error: (message: string) => void;
17
+ step: (message: string) => void;
18
+ };
19
+ };
20
+
21
+ async function loadClack(): Promise<ClackModule | null> {
22
+ try {
23
+ return (await dynamicImport("@clack/prompts")) as ClackModule;
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ export async function displayFixProposal(
30
+ proposal: FixProposal,
31
+ log: (message: string) => void
32
+ ): Promise<void> {
33
+ const errorCount = proposal.issues.filter((i) => i.level === "error").length;
34
+ const warningCount = proposal.issues.filter((i) => i.level === "warning").length;
35
+
36
+ log("");
37
+ log(`Fix Proposal`);
38
+ log(`${"─".repeat(50)}`);
39
+ log(`Issues found: ${errorCount} error(s), ${warningCount} warning(s)`);
40
+ log(`Artifacts to regenerate: ${proposal.targets.join(", ")}`);
41
+ log("");
42
+
43
+ for (const [type, issues] of proposal.issuesByArtifact) {
44
+ log(` ${type.toUpperCase()} (${issues.length} issue${issues.length > 1 ? "s" : ""}):`);
45
+ for (const issue of issues) {
46
+ const prefix = issue.level === "error" ? " ✖" : " ⚠";
47
+ log(` ${prefix} [${issue.code}] ${issue.message}`);
48
+ if (issue.suggestion) {
49
+ log(` → ${issue.suggestion}`);
50
+ }
51
+ }
52
+ log("");
53
+ }
54
+
55
+ const orphanIssues = proposal.issues.filter((i) => !i.artifactType);
56
+ if (orphanIssues.length > 0) {
57
+ log(` General (${orphanIssues.length} issue${orphanIssues.length > 1 ? "s" : ""}):`);
58
+ for (const issue of orphanIssues) {
59
+ const prefix = issue.level === "error" ? " ✖" : " ⚠";
60
+ log(` ${prefix} [${issue.code}] ${issue.message}`);
61
+ }
62
+ log("");
63
+ }
64
+ }
65
+
66
+ export async function confirmFixExecution(
67
+ proposal: FixProposal
68
+ ): Promise<boolean> {
69
+ if (process.stdout.isTTY !== true) {
70
+ return true;
71
+ }
72
+
73
+ const clack = await loadClack();
74
+ if (!clack) {
75
+ return true;
76
+ }
77
+
78
+ clack.intro("Prodo Fix");
79
+
80
+ const proceed = await clack.confirm({
81
+ message: `Regenerate ${proposal.targets.length} artifact(s): ${proposal.targets.join(", ")}?`
82
+ });
83
+
84
+ if (clack.isCancel(proceed)) {
85
+ clack.cancel("Fix cancelled.");
86
+ return false;
87
+ }
88
+
89
+ return proceed === true;
90
+ }
91
+
92
+ export async function displayFixResult(
93
+ result: FixResult,
94
+ log: (message: string) => void
95
+ ): Promise<void> {
96
+ log("");
97
+ if (result.finalPass) {
98
+ log("Fix complete — validation passed.");
99
+ } else {
100
+ log("Fix applied but validation still failing.");
101
+ log(`Review report: ${result.reportPath}`);
102
+ if (result.backupDir) {
103
+ log(`Backup saved: ${result.backupDir}`);
104
+ }
105
+ }
106
+
107
+ const clack = await loadClack();
108
+ if (clack && process.stdout.isTTY) {
109
+ clack.outro(result.finalPass ? "All gates passed!" : "Review report and retry.");
110
+ }
111
+ }