@shahmarasy/prodo 0.1.3 → 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 (205) 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/agents.js +4 -2
  18. package/dist/artifacts.d.ts +1 -0
  19. package/dist/artifacts.js +265 -31
  20. package/dist/cli/agent-command-installer.d.ts +4 -0
  21. package/dist/cli/agent-command-installer.js +148 -0
  22. package/dist/cli/agent-ids.d.ts +15 -0
  23. package/dist/cli/agent-ids.js +49 -0
  24. package/dist/cli/doctor.d.ts +1 -0
  25. package/dist/cli/doctor.js +144 -0
  26. package/dist/cli/fix-tui.d.ts +4 -0
  27. package/dist/cli/fix-tui.js +79 -0
  28. package/dist/cli/index.d.ts +9 -0
  29. package/dist/cli/index.js +465 -0
  30. package/dist/cli/init-tui.d.ts +23 -0
  31. package/dist/cli/init-tui.js +176 -0
  32. package/dist/cli/init.d.ts +11 -0
  33. package/dist/cli/init.js +334 -0
  34. package/dist/cli/normalize-interactive.d.ts +8 -0
  35. package/dist/cli/normalize-interactive.js +167 -0
  36. package/dist/cli/preset-loader.d.ts +4 -0
  37. package/dist/cli/preset-loader.js +210 -0
  38. package/dist/cli.js +80 -3
  39. package/dist/core/artifact-registry.d.ts +11 -0
  40. package/dist/core/artifact-registry.js +49 -0
  41. package/dist/core/artifacts.d.ts +10 -0
  42. package/dist/core/artifacts.js +892 -0
  43. package/dist/core/clean.d.ts +10 -0
  44. package/dist/core/clean.js +74 -0
  45. package/dist/core/consistency.d.ts +8 -0
  46. package/dist/core/consistency.js +328 -0
  47. package/dist/core/constants.d.ts +7 -0
  48. package/dist/core/constants.js +64 -0
  49. package/dist/core/errors.d.ts +3 -0
  50. package/dist/core/errors.js +10 -0
  51. package/dist/core/fix.d.ts +31 -0
  52. package/dist/core/fix.js +188 -0
  53. package/dist/core/hook-executor.d.ts +1 -0
  54. package/dist/core/hook-executor.js +175 -0
  55. package/dist/core/markdown.d.ts +16 -0
  56. package/dist/core/markdown.js +81 -0
  57. package/dist/core/normalize.d.ts +8 -0
  58. package/dist/core/normalize.js +125 -0
  59. package/dist/core/normalized-brief.d.ts +48 -0
  60. package/dist/core/normalized-brief.js +182 -0
  61. package/dist/core/output-index.d.ts +13 -0
  62. package/dist/core/output-index.js +55 -0
  63. package/dist/core/paths.d.ts +17 -0
  64. package/dist/core/paths.js +80 -0
  65. package/dist/core/project-config.d.ts +14 -0
  66. package/dist/core/project-config.js +69 -0
  67. package/dist/core/registry.d.ts +13 -0
  68. package/dist/core/registry.js +115 -0
  69. package/dist/core/settings.d.ts +7 -0
  70. package/dist/core/settings.js +35 -0
  71. package/dist/core/template-engine.d.ts +3 -0
  72. package/dist/core/template-engine.js +43 -0
  73. package/dist/core/template-resolver.d.ts +15 -0
  74. package/dist/core/template-resolver.js +46 -0
  75. package/dist/core/templates.d.ts +33 -0
  76. package/dist/core/templates.js +440 -0
  77. package/dist/core/terminology.d.ts +21 -0
  78. package/dist/core/terminology.js +143 -0
  79. package/dist/core/tracing.d.ts +21 -0
  80. package/dist/core/tracing.js +74 -0
  81. package/dist/core/types.d.ts +35 -0
  82. package/dist/core/types.js +5 -0
  83. package/dist/core/utils.d.ts +7 -0
  84. package/dist/core/utils.js +66 -0
  85. package/dist/core/validate.d.ts +10 -0
  86. package/dist/core/validate.js +226 -0
  87. package/dist/core/validator.d.ts +5 -0
  88. package/dist/core/validator.js +76 -0
  89. package/dist/core/version.d.ts +1 -0
  90. package/dist/core/version.js +30 -0
  91. package/dist/core/workflow-commands.d.ts +7 -0
  92. package/dist/core/workflow-commands.js +29 -0
  93. package/dist/i18n/en.json +45 -0
  94. package/dist/i18n/index.d.ts +5 -0
  95. package/dist/i18n/index.js +63 -0
  96. package/dist/i18n/tr.json +45 -0
  97. package/dist/init-tui.d.ts +3 -0
  98. package/dist/init-tui.js +28 -1
  99. package/dist/init.d.ts +1 -0
  100. package/dist/init.js +9 -3
  101. package/dist/normalize.js +55 -7
  102. package/dist/providers/index.d.ts +2 -1
  103. package/dist/providers/index.js +20 -6
  104. package/dist/providers/mock-provider.d.ts +1 -1
  105. package/dist/providers/mock-provider.js +7 -6
  106. package/dist/providers/openai-provider.d.ts +1 -1
  107. package/dist/providers/openai-provider.js +3 -2
  108. package/dist/settings.d.ts +1 -0
  109. package/dist/settings.js +2 -1
  110. package/dist/skills/engine.d.ts +10 -0
  111. package/dist/skills/engine.js +75 -0
  112. package/dist/skills/fix-skill.d.ts +2 -0
  113. package/dist/skills/fix-skill.js +38 -0
  114. package/dist/skills/generate-artifact-skill.d.ts +2 -0
  115. package/dist/skills/generate-artifact-skill.js +32 -0
  116. package/dist/skills/generate-pipeline-skill.d.ts +2 -0
  117. package/dist/skills/generate-pipeline-skill.js +45 -0
  118. package/dist/skills/normalize-skill.d.ts +2 -0
  119. package/dist/skills/normalize-skill.js +29 -0
  120. package/dist/skills/types.d.ts +28 -0
  121. package/dist/skills/types.js +2 -0
  122. package/dist/skills/validate-skill.d.ts +2 -0
  123. package/dist/skills/validate-skill.js +29 -0
  124. package/dist/templates.d.ts +1 -1
  125. package/dist/templates.js +2 -0
  126. package/dist/utils.d.ts +1 -0
  127. package/dist/utils.js +13 -0
  128. package/dist/validator.js +0 -4
  129. package/dist/workflow-commands.js +2 -1
  130. package/package.json +74 -45
  131. package/presets/fintech/preset.json +48 -1
  132. package/presets/fintech/prompts/prd.md +99 -2
  133. package/presets/marketplace/preset.json +51 -1
  134. package/presets/marketplace/prompts/prd.md +140 -2
  135. package/presets/saas/preset.json +53 -1
  136. package/presets/saas/prompts/prd.md +150 -2
  137. package/src/agents/agent-registry.ts +93 -0
  138. package/src/agents/anthropic/index.ts +86 -0
  139. package/src/agents/anthropic/manifest.json +7 -0
  140. package/src/agents/base.ts +77 -0
  141. package/src/agents/google/index.ts +79 -0
  142. package/src/agents/google/manifest.json +7 -0
  143. package/src/agents/mock/index.ts +32 -0
  144. package/src/agents/mock/manifest.json +7 -0
  145. package/src/agents/openai/index.ts +83 -0
  146. package/src/agents/openai/manifest.json +7 -0
  147. package/src/agents/system-prompts.ts +35 -0
  148. package/src/{agent-command-installer.ts → cli/agent-command-installer.ts} +164 -164
  149. package/src/{agents.ts → cli/agent-ids.ts} +58 -56
  150. package/src/{doctor.ts → cli/doctor.ts} +157 -137
  151. package/src/cli/fix-tui.ts +111 -0
  152. package/src/{cli.ts → cli/index.ts} +459 -319
  153. package/src/{init-tui.ts → cli/init-tui.ts} +208 -179
  154. package/src/{init.ts → cli/init.ts} +398 -391
  155. package/src/cli/normalize-interactive.ts +241 -0
  156. package/src/{preset-loader.ts → cli/preset-loader.ts} +237 -237
  157. package/src/{artifact-registry.ts → core/artifact-registry.ts} +69 -69
  158. package/src/{artifacts.ts → core/artifacts.ts} +1081 -777
  159. package/src/core/clean.ts +88 -0
  160. package/src/{consistency.ts → core/consistency.ts} +374 -303
  161. package/src/{constants.ts → core/constants.ts} +72 -72
  162. package/src/{errors.ts → core/errors.ts} +7 -7
  163. package/src/core/fix.ts +253 -0
  164. package/src/{hook-executor.ts → core/hook-executor.ts} +196 -196
  165. package/src/{markdown.ts → core/markdown.ts} +93 -73
  166. package/src/core/normalize.ts +145 -0
  167. package/src/{normalized-brief.ts → core/normalized-brief.ts} +227 -206
  168. package/src/{output-index.ts → core/output-index.ts} +59 -59
  169. package/src/{paths.ts → core/paths.ts} +75 -71
  170. package/src/{project-config.ts → core/project-config.ts} +78 -78
  171. package/src/{registry.ts → core/registry.ts} +119 -119
  172. package/src/{settings.ts → core/settings.ts} +35 -34
  173. package/src/core/template-engine.ts +45 -0
  174. package/src/{template-resolver.ts → core/template-resolver.ts} +54 -54
  175. package/src/{templates.ts → core/templates.ts} +452 -450
  176. package/src/core/terminology.ts +177 -0
  177. package/src/core/tracing.ts +110 -0
  178. package/src/{types.ts → core/types.ts} +46 -46
  179. package/src/{utils.ts → core/utils.ts} +64 -50
  180. package/src/{validate.ts → core/validate.ts} +252 -246
  181. package/src/{validator.ts → core/validator.ts} +92 -96
  182. package/src/{version.ts → core/version.ts} +24 -24
  183. package/src/{workflow-commands.ts → core/workflow-commands.ts} +32 -31
  184. package/src/i18n/en.json +45 -0
  185. package/src/i18n/index.ts +58 -0
  186. package/src/i18n/tr.json +45 -0
  187. package/src/providers/index.ts +29 -12
  188. package/src/providers/mock-provider.ts +200 -199
  189. package/src/providers/openai-provider.ts +88 -87
  190. package/src/skills/engine.ts +94 -0
  191. package/src/skills/fix-skill.ts +38 -0
  192. package/src/skills/generate-artifact-skill.ts +32 -0
  193. package/src/skills/generate-pipeline-skill.ts +49 -0
  194. package/src/skills/normalize-skill.ts +29 -0
  195. package/src/skills/types.ts +36 -0
  196. package/src/skills/validate-skill.ts +29 -0
  197. package/templates/commands/prodo-fix.md +46 -0
  198. package/templates/commands/prodo-normalize.md +118 -23
  199. package/templates/commands/prodo-prd.md +138 -17
  200. package/templates/commands/prodo-stories.md +153 -17
  201. package/templates/commands/prodo-techspec.md +167 -17
  202. package/templates/commands/prodo-validate.md +184 -26
  203. package/templates/commands/prodo-wireframe.md +188 -17
  204. package/templates/commands/prodo-workflow.md +200 -17
  205. package/src/normalize.ts +0 -89
@@ -0,0 +1,465 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runCli = runCli;
40
+ const commander_1 = require("commander");
41
+ const node_crypto_1 = require("node:crypto");
42
+ const promises_1 = __importDefault(require("node:fs/promises"));
43
+ const node_path_1 = __importDefault(require("node:path"));
44
+ const agent_ids_1 = require("./agent-ids");
45
+ const artifact_registry_1 = require("../core/artifact-registry");
46
+ const artifacts_1 = require("../core/artifacts");
47
+ const doctor_1 = require("./doctor");
48
+ const errors_1 = require("../core/errors");
49
+ const hook_executor_1 = require("../core/hook-executor");
50
+ const init_1 = require("./init");
51
+ const init_tui_1 = require("./init-tui");
52
+ const clean_1 = require("../core/clean");
53
+ const fix_1 = require("../core/fix");
54
+ const normalize_1 = require("../core/normalize");
55
+ const normalize_interactive_1 = require("./normalize-interactive");
56
+ const fix_tui_1 = require("./fix-tui");
57
+ const paths_1 = require("../core/paths");
58
+ const utils_1 = require("../core/utils");
59
+ const validate_1 = require("../core/validate");
60
+ const version_1 = require("../core/version");
61
+ const dynamicImport = new Function("specifier", "return import(specifier)");
62
+ function mapForcedCommand(forcedCommand) {
63
+ if (forcedCommand === "prodo-init")
64
+ return "init";
65
+ if (forcedCommand === "prodo-validate")
66
+ return "validate";
67
+ if (forcedCommand === "prodo-normalize")
68
+ return "normalize";
69
+ if (forcedCommand === "prodo-fix")
70
+ return "fix";
71
+ if (forcedCommand === "prodo-prd")
72
+ return "prd";
73
+ if (forcedCommand === "prodo-workflow")
74
+ return "workflow";
75
+ if (forcedCommand === "prodo-wireframe")
76
+ return "wireframe";
77
+ if (forcedCommand === "prodo-stories")
78
+ return "stories";
79
+ if (forcedCommand === "prodo-techspec")
80
+ return "techspec";
81
+ return undefined;
82
+ }
83
+ async function runArtifactCommand(type, opts, cwd, log, options) {
84
+ await (0, hook_executor_1.runHookPhase)(cwd, `before_${type}`, log);
85
+ const agent = (0, agent_ids_1.resolveAgent)(opts.agent);
86
+ const file = await (0, artifacts_1.generateArtifact)({
87
+ artifactType: type,
88
+ cwd,
89
+ normalizedBriefOverride: opts.from,
90
+ outPath: opts.out,
91
+ agent,
92
+ revisionType: opts.revisionType
93
+ });
94
+ const agentMsg = agent ? ` [agent=${agent}]` : "";
95
+ log(`${type.toUpperCase()} generated${agentMsg}: ${file}`);
96
+ if (options?.suggestValidate !== false) {
97
+ log("Tip: run `prodo validate` to check cross-artifact consistency.");
98
+ }
99
+ await (0, hook_executor_1.runHookPhase)(cwd, `after_${type}`, log);
100
+ }
101
+ async function snapshotBrief(cwd) {
102
+ const file = (0, paths_1.briefPath)(cwd);
103
+ if (!(await (0, utils_1.fileExists)(file)))
104
+ return null;
105
+ const [raw, stat] = await Promise.all([promises_1.default.readFile(file), promises_1.default.stat(file)]);
106
+ return {
107
+ hash: (0, node_crypto_1.createHash)("sha256").update(raw).digest("hex"),
108
+ mtimeMs: stat.mtimeMs,
109
+ size: stat.size
110
+ };
111
+ }
112
+ async function withBriefReadOnlyGuard(cwd, task) {
113
+ const before = await snapshotBrief(cwd);
114
+ await task();
115
+ const after = await snapshotBrief(cwd);
116
+ if (!before)
117
+ return;
118
+ if (!after) {
119
+ throw new errors_1.UserError("Input file `brief.md` was removed during execution. Input files are read-only.");
120
+ }
121
+ if (before.hash !== after.hash || before.size !== after.size || before.mtimeMs !== after.mtimeMs) {
122
+ throw new errors_1.UserError("Input file `brief.md` was modified during execution. Input files are read-only.");
123
+ }
124
+ }
125
+ async function runCli(options = {}) {
126
+ const cwd = options.cwd ?? process.cwd();
127
+ const argv = options.argv ?? process.argv;
128
+ const out = options.log ?? console.log;
129
+ const err = options.error ?? console.error;
130
+ const forced = options.forcedCommand ? mapForcedCommand(options.forcedCommand) : undefined;
131
+ const program = new commander_1.Command();
132
+ const version = await (0, version_1.readCliVersion)(cwd);
133
+ program
134
+ .name("prodo")
135
+ .description("CLI-first, prompt-powered product artifact kit")
136
+ .version(`prodo ${version}`, "-v, --version", "Show Prodo version")
137
+ .showHelpAfterError();
138
+ const artifactTypes = await (0, artifact_registry_1.listArtifactTypes)(cwd);
139
+ program
140
+ .command("init [target]")
141
+ .option("--ai <name>", "agent integration: codex | gemini-cli | claude-cli")
142
+ .option("--lang <code>", "document language (e.g. en, tr)")
143
+ .option("--author <name>", "document author name")
144
+ .option("--preset <name>", "preset to install during initialization")
145
+ .action(async (target, opts) => {
146
+ const projectRoot = node_path_1.default.resolve(cwd, target ?? ".");
147
+ const selected = await (0, init_tui_1.gatherInitSelections)({
148
+ projectRoot,
149
+ aiInput: opts.ai,
150
+ langInput: opts.lang,
151
+ authorInput: opts.author
152
+ });
153
+ const selectedAi = selected.ai;
154
+ if (selected.interactive) {
155
+ const clack = (await dynamicImport("@clack/prompts"));
156
+ const s = clack.spinner();
157
+ s.start("Scaffolding Prodo workspace...");
158
+ const result = await (0, init_1.runInit)(projectRoot, {
159
+ ai: selectedAi,
160
+ lang: selected.lang,
161
+ author: selected.author,
162
+ preset: opts.preset,
163
+ script: selected.script
164
+ });
165
+ s.stop("Scaffold complete.");
166
+ await (0, init_tui_1.finishInitInteractive)({
167
+ projectRoot,
168
+ settingsPath: result.settingsPath,
169
+ ai: selectedAi,
170
+ lang: selected.lang,
171
+ author: selected.author
172
+ });
173
+ return;
174
+ }
175
+ const result = await (0, init_1.runInit)(projectRoot, {
176
+ ai: selectedAi,
177
+ lang: selected.lang,
178
+ author: selected.author,
179
+ preset: opts.preset,
180
+ script: selected.script
181
+ });
182
+ out(`Initialized Prodo scaffold at ${node_path_1.default.join(projectRoot, ".prodo")}`);
183
+ if (selectedAi) {
184
+ out(`Agent command set installed for ${selectedAi}.`);
185
+ out(`Installed ${result.installedAgentFiles.length} command files.`);
186
+ out("Agent workflow: edit brief.md, then run slash commands in your agent.");
187
+ }
188
+ else {
189
+ out("No agent selected. Use `prodo generate` for end-to-end generation.");
190
+ }
191
+ out(`Settings file: ${result.settingsPath}`);
192
+ out(`Author: ${selected.author}`);
193
+ out("Next: edit brief.md.");
194
+ });
195
+ program
196
+ .command("generate")
197
+ .description("Run end-to-end pipeline: normalize -> generate artifacts -> validate")
198
+ .option("--agent <name>", "agent profile: codex | gemini-cli | claude-cli")
199
+ .option("--strict", "treat validation warnings as errors")
200
+ .option("--report <path>", "validation report output path")
201
+ .option("--dry-run", "show what would be generated without writing files")
202
+ .action(async (opts) => {
203
+ if (opts.agent)
204
+ (0, agent_ids_1.resolveAgent)(opts.agent);
205
+ if (opts.dryRun) {
206
+ out("[Dry Run] Pipeline would execute:");
207
+ out(` 1. Normalize brief.md`);
208
+ for (const type of artifactTypes) {
209
+ out(` 2. Generate ${type}`);
210
+ }
211
+ out(` 3. Validate all artifacts`);
212
+ out(`\nArtifact types: ${artifactTypes.join(", ")}`);
213
+ return;
214
+ }
215
+ await withBriefReadOnlyGuard(cwd, async () => {
216
+ await (0, hook_executor_1.runHookPhase)(cwd, "before_normalize", out);
217
+ const normalizedPath = await (0, normalize_1.runNormalize)({ cwd });
218
+ out(`Normalized brief written to: ${normalizedPath}`);
219
+ await (0, hook_executor_1.runHookPhase)(cwd, "after_normalize", out);
220
+ for (const type of artifactTypes) {
221
+ await runArtifactCommand(type, { from: normalizedPath, agent: opts.agent }, cwd, out, {
222
+ suggestValidate: false
223
+ });
224
+ }
225
+ await (0, hook_executor_1.runHookPhase)(cwd, "before_validate", out);
226
+ const result = await (0, validate_1.runValidate)(cwd, {
227
+ strict: Boolean(opts.strict),
228
+ report: opts.report
229
+ });
230
+ out(`Validation report written to: ${result.reportPath}`);
231
+ if (!result.pass) {
232
+ throw new errors_1.UserError("Validation failed. Review report and fix issues.");
233
+ }
234
+ out("Generation pipeline completed. Validation passed.");
235
+ await (0, hook_executor_1.runHookPhase)(cwd, "after_validate", out);
236
+ });
237
+ });
238
+ program
239
+ .command("fix", { hidden: true })
240
+ .description("Advanced: auto-regenerate affected artifacts from validation findings")
241
+ .option("--agent <name>", "agent profile: codex | gemini-cli | claude-cli")
242
+ .option("--strict", "treat validation warnings as errors")
243
+ .option("--report <path>", "validation report output path")
244
+ .option("--dry-run", "show fix proposal without applying changes")
245
+ .action(async (opts) => {
246
+ if (opts.agent)
247
+ (0, agent_ids_1.resolveAgent)(opts.agent);
248
+ await withBriefReadOnlyGuard(cwd, async () => {
249
+ const fixOpts = {
250
+ cwd,
251
+ agent: opts.agent,
252
+ strict: Boolean(opts.strict),
253
+ report: opts.report,
254
+ dryRun: Boolean(opts.dryRun),
255
+ log: out
256
+ };
257
+ if (opts.dryRun) {
258
+ const result = await (0, fix_1.runFix)(fixOpts);
259
+ out(`Validation report: ${result.reportPath}`);
260
+ if (result.proposal.targets.length > 0) {
261
+ await (0, fix_tui_1.displayFixProposal)(result.proposal, out);
262
+ }
263
+ return;
264
+ }
265
+ const proposal = await (0, fix_1.buildFixProposal)(fixOpts);
266
+ out(`Validation report: ${proposal.initialReport.reportPath}`);
267
+ if (proposal.targets.length === 0) {
268
+ out("No blocking issues found. Nothing to fix.");
269
+ return;
270
+ }
271
+ await (0, fix_tui_1.displayFixProposal)(proposal, out);
272
+ const confirmed = await (0, fix_tui_1.confirmFixExecution)(proposal);
273
+ if (!confirmed) {
274
+ out("Fix cancelled by user.");
275
+ return;
276
+ }
277
+ out(`Regenerating impacted artifacts: ${proposal.targets.join(", ")}`);
278
+ const result = await (0, fix_1.applyFix)(cwd, proposal, fixOpts);
279
+ await (0, fix_tui_1.displayFixResult)(result, out);
280
+ if (!result.finalPass) {
281
+ throw new errors_1.UserError("Fix completed but validation is still failing. Review report and retry.");
282
+ }
283
+ });
284
+ });
285
+ program
286
+ .command("normalize", { hidden: true })
287
+ .description("Advanced: normalize brief without full pipeline")
288
+ .option("--brief <path>", "path to start brief markdown")
289
+ .option("--out <path>", "output normalized brief json path")
290
+ .option("--agent <name>", "agent profile: codex | gemini-cli | claude-cli")
291
+ .option("-i, --interactive", "interactively clarify low-confidence fields")
292
+ .option("--dry-run", "show what would be normalized without writing")
293
+ .action(async (opts) => {
294
+ if (opts.agent)
295
+ (0, agent_ids_1.resolveAgent)(opts.agent);
296
+ if (opts.dryRun) {
297
+ const briefFile = opts.brief ?? "brief.md";
298
+ out(`[Dry Run] Would normalize: ${briefFile}`);
299
+ out(`[Dry Run] Output would be written to: .prodo/briefs/normalized-brief.json`);
300
+ return;
301
+ }
302
+ await withBriefReadOnlyGuard(cwd, async () => {
303
+ await (0, hook_executor_1.runHookPhase)(cwd, "before_normalize", out);
304
+ const outPath = opts.interactive
305
+ ? await (0, normalize_interactive_1.runInteractiveNormalize)({ cwd, brief: opts.brief, out: opts.out, log: out })
306
+ : await (0, normalize_1.runNormalize)({ cwd, brief: opts.brief, out: opts.out });
307
+ out(`Normalized brief written to: ${outPath}`);
308
+ await (0, hook_executor_1.runHookPhase)(cwd, "after_normalize", out);
309
+ });
310
+ });
311
+ program
312
+ .command("doctor")
313
+ .alias("check")
314
+ .description("Check local environment and toolchain readiness")
315
+ .action(async () => {
316
+ await (0, doctor_1.runDoctor)(cwd, out);
317
+ });
318
+ program
319
+ .command("clean")
320
+ .description("Remove all generated artifacts, keep brief.md and config")
321
+ .option("--dry-run", "show what would be removed without deleting")
322
+ .action(async (opts) => {
323
+ const result = await (0, clean_1.runClean)({
324
+ cwd,
325
+ dryRun: Boolean(opts.dryRun),
326
+ log: out
327
+ });
328
+ if (result.removedPaths.length === 0) {
329
+ out("Nothing to clean.");
330
+ }
331
+ else if (!opts.dryRun) {
332
+ out(`Cleaned ${result.removedPaths.length} path(s). Project is ready for a fresh run.`);
333
+ }
334
+ });
335
+ for (const type of artifactTypes) {
336
+ program
337
+ .command(type, { hidden: true })
338
+ .description(`Advanced: generate only ${type} artifact`)
339
+ .option("--from <path>", "path to normalized-brief.json")
340
+ .option("--out <path>", "output file path")
341
+ .option("--agent <name>", "agent profile: codex | gemini-cli | claude-cli")
342
+ .action(async (opts) => {
343
+ await withBriefReadOnlyGuard(cwd, async () => {
344
+ await runArtifactCommand(type, opts, cwd, out);
345
+ });
346
+ });
347
+ }
348
+ program
349
+ .command("agent-commands", { hidden: true })
350
+ .requiredOption("--agent <name>", "agent profile: codex | gemini-cli | claude-cli")
351
+ .action(async (opts) => {
352
+ const agent = (0, agent_ids_1.resolveAgent)(opts.agent);
353
+ if (!agent)
354
+ throw new errors_1.UserError("Agent is required.");
355
+ const set = await (0, agent_ids_1.loadAgentCommandSet)(cwd, agent);
356
+ out(`Agent: ${set.agent}`);
357
+ if (set.description)
358
+ out(`Description: ${set.description}`);
359
+ out("");
360
+ out("Recommended sequence:");
361
+ for (const item of set.recommended_sequence ?? []) {
362
+ out(`- ${item.command}: ${item.purpose}`);
363
+ }
364
+ if (set.artifact_shortcuts) {
365
+ out("");
366
+ out("Artifact shortcuts:");
367
+ for (const [key, command] of Object.entries(set.artifact_shortcuts)) {
368
+ out(`- ${key}: ${command}`);
369
+ }
370
+ }
371
+ });
372
+ program
373
+ .command("validate", { hidden: true })
374
+ .description("Advanced: run validation only")
375
+ .option("--strict", "treat warnings as errors")
376
+ .option("--report <path>", "report output path")
377
+ .option("--agent <name>", "agent profile: codex | gemini-cli | claude-cli")
378
+ .action(async (opts) => {
379
+ if (opts.agent)
380
+ (0, agent_ids_1.resolveAgent)(opts.agent);
381
+ await withBriefReadOnlyGuard(cwd, async () => {
382
+ await (0, hook_executor_1.runHookPhase)(cwd, "before_validate", out);
383
+ const result = await (0, validate_1.runValidate)(cwd, {
384
+ strict: Boolean(opts.strict),
385
+ report: opts.report
386
+ });
387
+ out(`Validation report written to: ${result.reportPath}`);
388
+ if (!result.pass) {
389
+ throw new errors_1.UserError("Validation failed. Review report and fix issues.");
390
+ }
391
+ out("Validation passed.");
392
+ await (0, hook_executor_1.runHookPhase)(cwd, "after_validate", out);
393
+ });
394
+ });
395
+ program
396
+ .command("skills", { hidden: true })
397
+ .description("Advanced: manage and run skills")
398
+ .argument("[action]", "list or run", "list")
399
+ .argument("[name]", "skill name (for run)")
400
+ .option("--input <json>", "JSON input for skill execution")
401
+ .action(async (action, name, opts) => {
402
+ const { getGlobalSkillEngine } = await Promise.resolve().then(() => __importStar(require("../skills/engine")));
403
+ const engine = getGlobalSkillEngine();
404
+ if (action === "list") {
405
+ const manifests = engine.listSkills();
406
+ if (manifests.length === 0) {
407
+ out("No skills registered.");
408
+ return;
409
+ }
410
+ out("Available skills:\n");
411
+ for (const m of manifests) {
412
+ out(` ${m.name.padEnd(25)} [${m.category}] ${m.description}`);
413
+ }
414
+ return;
415
+ }
416
+ if (action === "run") {
417
+ if (!name)
418
+ throw new errors_1.UserError("Skill name is required. Usage: prodo skills run <name>");
419
+ const inputs = opts.input ? JSON.parse(opts.input) : {};
420
+ inputs.cwd = inputs.cwd ?? cwd;
421
+ const result = await engine.execute(name, { cwd, log: out }, inputs);
422
+ out(`\nSkill "${name}" completed.`);
423
+ out(JSON.stringify(result, null, 2));
424
+ return;
425
+ }
426
+ throw new errors_1.UserError(`Unknown skills action: "${action}". Use: list or run`);
427
+ });
428
+ try {
429
+ if (forced) {
430
+ if (forced === "init") {
431
+ await program.parseAsync(["node", "prodo", "init", ...argv.slice(2)]);
432
+ }
433
+ else if (forced === "normalize") {
434
+ await program.parseAsync(["node", "prodo", "normalize", ...argv.slice(2)]);
435
+ }
436
+ else if (forced === "validate") {
437
+ await program.parseAsync(["node", "prodo", "validate", ...argv.slice(2)]);
438
+ }
439
+ else if (forced === "fix") {
440
+ await program.parseAsync(["node", "prodo", "fix", ...argv.slice(2)]);
441
+ }
442
+ else {
443
+ await program.parseAsync(["node", "prodo", forced, ...argv.slice(2)]);
444
+ }
445
+ }
446
+ else {
447
+ await program.parseAsync(argv);
448
+ }
449
+ return 0;
450
+ }
451
+ catch (error) {
452
+ if (error instanceof errors_1.UserError) {
453
+ err(error.message);
454
+ return 1;
455
+ }
456
+ const unknown = error;
457
+ err(unknown.message);
458
+ return 1;
459
+ }
460
+ }
461
+ if (require.main === module) {
462
+ runCli().then((code) => {
463
+ process.exitCode = code;
464
+ });
465
+ }
@@ -0,0 +1,23 @@
1
+ import { type SupportedAi } from "./agent-command-installer";
2
+ export type InitSelections = {
3
+ ai?: SupportedAi;
4
+ script: "sh" | "ps";
5
+ lang: "tr" | "en";
6
+ author: string;
7
+ interactive: boolean;
8
+ };
9
+ type GatherInitUiOptions = {
10
+ projectRoot: string;
11
+ aiInput?: string;
12
+ langInput?: string;
13
+ authorInput?: string;
14
+ };
15
+ export declare function gatherInitSelections(options: GatherInitUiOptions): Promise<InitSelections>;
16
+ export declare function finishInitInteractive(summary: {
17
+ projectRoot: string;
18
+ settingsPath: string;
19
+ ai?: SupportedAi;
20
+ lang: "tr" | "en";
21
+ author: string;
22
+ }): Promise<void>;
23
+ export {};
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.gatherInitSelections = gatherInitSelections;
7
+ exports.finishInitInteractive = finishInitInteractive;
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const agent_command_installer_1 = require("./agent-command-installer");
11
+ const errors_1 = require("../core/errors");
12
+ const utils_1 = require("../core/utils");
13
+ const dynamicImport = new Function("specifier", "return import(specifier)");
14
+ async function loadClack() {
15
+ return (await dynamicImport("@clack/prompts"));
16
+ }
17
+ function isInteractiveTerminal() {
18
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
19
+ }
20
+ function color(text, code) {
21
+ return `${code}${text}\u001B[0m`;
22
+ }
23
+ function stripAnsi(input) {
24
+ return input.replace(/\u001B\[[0-9;]*m/g, "");
25
+ }
26
+ function centerLine(line, width) {
27
+ const visible = stripAnsi(line).length;
28
+ const left = Math.max(0, Math.floor((width - visible) / 2));
29
+ return `${" ".repeat(left)}${line}`;
30
+ }
31
+ function centerBlock(lines, width) {
32
+ return lines.map((line) => centerLine(line, width)).join("\n");
33
+ }
34
+ function terminalWidth() {
35
+ const columns = process.stdout.columns ?? 100;
36
+ return Math.max(80, columns);
37
+ }
38
+ function renderLogo() {
39
+ const cyan = "\u001B[38;5;45m";
40
+ const blue = "\u001B[38;5;39m";
41
+ const lines = [
42
+ "██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ",
43
+ "██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██╔═══██╗",
44
+ "██████╔╝██████╔╝██║ ██║██║ ██║██║ ██║",
45
+ "██╔═══╝ ██╔══██╗██║ ██║██║ ██║██║ ██║",
46
+ "██║ ██║ ██║╚██████╔╝██████╔╝╚██████╔╝"
47
+ ];
48
+ return lines.map((line, idx) => color(line, idx % 2 === 0 ? cyan : blue)).join("\n");
49
+ }
50
+ function renderProjectBox(projectName, projectRoot) {
51
+ const content = [`Project ${projectName}`, `Directory ${projectRoot}`];
52
+ const innerWidth = Math.max(...content.map((line) => line.length)) + 2;
53
+ const top = `┌${"─".repeat(innerWidth)}┐`;
54
+ const rows = content.map((line) => `│ ${line.padEnd(innerWidth - 1)}│`);
55
+ const bottom = `└${"─".repeat(innerWidth)}┘`;
56
+ return [top, ...rows, bottom].join("\n");
57
+ }
58
+ async function detectAi(projectRoot) {
59
+ if (await (0, utils_1.fileExists)(node_path_1.default.join(projectRoot, ".agents")))
60
+ return "codex";
61
+ if (await (0, utils_1.fileExists)(node_path_1.default.join(projectRoot, ".gemini")))
62
+ return "gemini-cli";
63
+ if (await (0, utils_1.fileExists)(node_path_1.default.join(projectRoot, ".claude")))
64
+ return "claude-cli";
65
+ return undefined;
66
+ }
67
+ function normalizeLang(lang) {
68
+ if ((lang ?? "").trim().toLowerCase().startsWith("tr"))
69
+ return "tr";
70
+ return "en";
71
+ }
72
+ function modelChoiceFromAi(ai) {
73
+ if (ai === "codex")
74
+ return "codex";
75
+ if (ai === "gemini-cli")
76
+ return "gemini";
77
+ return "auto-detect";
78
+ }
79
+ function defaultAuthorName(authorInput) {
80
+ const explicit = (authorInput ?? "").trim();
81
+ if (explicit.length > 0)
82
+ return explicit;
83
+ try {
84
+ const username = node_os_1.default.userInfo().username.trim();
85
+ if (username.length > 0)
86
+ return username;
87
+ }
88
+ catch {
89
+ // ignore lookup errors and continue with fallback
90
+ }
91
+ return "Product Author";
92
+ }
93
+ async function gatherInitSelections(options) {
94
+ const clack = await loadClack();
95
+ const defaultLang = normalizeLang(options.langInput);
96
+ const fallbackScript = process.platform === "win32" ? "ps" : "sh";
97
+ const parsedAi = (0, agent_command_installer_1.resolveAi)(options.aiInput);
98
+ const defaultAuthor = defaultAuthorName(options.authorInput);
99
+ if (!isInteractiveTerminal()) {
100
+ return {
101
+ ai: parsedAi,
102
+ script: fallbackScript,
103
+ lang: defaultLang,
104
+ author: defaultAuthor,
105
+ interactive: false
106
+ };
107
+ }
108
+ const detectedAi = await detectAi(options.projectRoot);
109
+ const initialModel = modelChoiceFromAi(parsedAi ?? detectedAi);
110
+ const projectName = node_path_1.default.basename(options.projectRoot) || ".";
111
+ const width = terminalWidth();
112
+ const subtitle = color("Prodo — Product Artifact Toolkit", "\u001B[1;37m");
113
+ const signature = color("Crafted by Codex, guided by Shahmarasy intelligence", "\u001B[38;5;244m");
114
+ const hero = [
115
+ "",
116
+ centerBlock(renderLogo().split("\n"), width),
117
+ "",
118
+ centerLine(subtitle, width),
119
+ centerLine(signature, width),
120
+ ""
121
+ ].join("\n");
122
+ clack.intro(hero);
123
+ clack.note(renderProjectBox(projectName, options.projectRoot), "Project Setup");
124
+ const model = await clack.select({
125
+ message: "Select model",
126
+ initialValue: initialModel,
127
+ options: [
128
+ { value: "codex", label: "codex", hint: "Native Codex flow" },
129
+ { value: "gemini", label: "gemini", hint: "Gemini CLI command set" },
130
+ { value: "auto-detect", label: "auto-detect", hint: "Detect from local agent directories" }
131
+ ]
132
+ });
133
+ if (clack.isCancel(model)) {
134
+ clack.cancel("Initialization cancelled.");
135
+ throw new errors_1.UserError("Initialization cancelled.");
136
+ }
137
+ const lang = await clack.select({
138
+ message: "Select language",
139
+ initialValue: defaultLang,
140
+ options: [
141
+ { value: "tr", label: "tr", hint: "Turkish" },
142
+ { value: "en", label: "en", hint: "English" }
143
+ ]
144
+ });
145
+ if (clack.isCancel(lang)) {
146
+ clack.cancel("Initialization cancelled.");
147
+ throw new errors_1.UserError("Initialization cancelled.");
148
+ }
149
+ const author = await clack.text({
150
+ message: "Author name",
151
+ placeholder: "Shahmarasy",
152
+ defaultValue: defaultAuthor
153
+ });
154
+ if (clack.isCancel(author)) {
155
+ clack.cancel("Initialization cancelled.");
156
+ throw new errors_1.UserError("Initialization cancelled.");
157
+ }
158
+ let selectedAi;
159
+ if (model === "codex")
160
+ selectedAi = "codex";
161
+ else if (model === "gemini")
162
+ selectedAi = "gemini-cli";
163
+ else
164
+ selectedAi = detectedAi;
165
+ return {
166
+ ai: selectedAi,
167
+ script: fallbackScript,
168
+ lang,
169
+ author: String(author).trim() || defaultAuthor,
170
+ interactive: true
171
+ };
172
+ }
173
+ function finishInitInteractive(summary) {
174
+ const aiText = summary.ai ?? "none";
175
+ return loadClack().then((clack) => clack.outro(`Scaffold complete.\nAI: ${aiText}\nLanguage: ${summary.lang}\nAuthor: ${summary.author}\nSettings: ${summary.settingsPath}\nNext: edit brief.md`));
176
+ }
@@ -0,0 +1,11 @@
1
+ import { type SupportedAi } from "./agent-command-installer";
2
+ export declare function runInit(cwd: string, options?: {
3
+ ai?: SupportedAi;
4
+ lang?: string;
5
+ author?: string;
6
+ preset?: string;
7
+ script?: "sh" | "ps";
8
+ }): Promise<{
9
+ installedAgentFiles: string[];
10
+ settingsPath: string;
11
+ }>;