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