@shahmarasy/prodo 0.1.2 → 0.1.4
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/dist/agents.js +4 -2
- package/dist/artifacts.d.ts +1 -0
- package/dist/artifacts.js +265 -31
- package/dist/cli.js +80 -3
- package/dist/init-tui.d.ts +3 -0
- package/dist/init-tui.js +28 -1
- package/dist/init.d.ts +1 -0
- package/dist/init.js +9 -3
- package/dist/normalize.js +55 -7
- package/dist/providers/openai-provider.js +2 -1
- package/dist/settings.d.ts +1 -0
- package/dist/settings.js +2 -1
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +11 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +13 -0
- package/dist/validator.js +0 -4
- package/dist/workflow-commands.js +2 -1
- package/package.json +1 -1
- package/presets/fintech/preset.json +48 -1
- package/presets/fintech/prompts/prd.md +99 -2
- package/presets/marketplace/preset.json +51 -1
- package/presets/marketplace/prompts/prd.md +140 -2
- package/presets/saas/preset.json +53 -1
- package/presets/saas/prompts/prd.md +150 -2
- package/src/agents.ts +4 -2
- package/src/artifacts.ts +323 -28
- package/src/cli.ts +97 -6
- package/src/init-tui.ts +30 -1
- package/src/init.ts +11 -4
- package/src/normalize.ts +55 -7
- package/src/providers/openai-provider.ts +2 -1
- package/src/settings.ts +3 -2
- package/src/templates.ts +12 -0
- package/src/utils.ts +14 -0
- package/src/validator.ts +0 -4
- package/src/workflow-commands.ts +2 -1
- package/templates/commands/prodo-fix.md +46 -0
- package/templates/commands/prodo-normalize.md +116 -14
- package/templates/commands/prodo-prd.md +136 -12
- package/templates/commands/prodo-stories.md +151 -12
- package/templates/commands/prodo-techspec.md +165 -12
- package/templates/commands/prodo-validate.md +184 -23
- package/templates/commands/prodo-wireframe.md +186 -12
- package/templates/commands/prodo-workflow.md +198 -12
package/src/init-tui.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import { resolveAi, type SupportedAi } from "./agent-command-installer";
|
|
3
4
|
import { UserError } from "./errors";
|
|
4
5
|
import { fileExists } from "./utils";
|
|
@@ -7,6 +8,7 @@ export type InitSelections = {
|
|
|
7
8
|
ai?: SupportedAi;
|
|
8
9
|
script: "sh" | "ps";
|
|
9
10
|
lang: "tr" | "en";
|
|
11
|
+
author: string;
|
|
10
12
|
interactive: boolean;
|
|
11
13
|
};
|
|
12
14
|
|
|
@@ -14,6 +16,7 @@ type GatherInitUiOptions = {
|
|
|
14
16
|
projectRoot: string;
|
|
15
17
|
aiInput?: string;
|
|
16
18
|
langInput?: string;
|
|
19
|
+
authorInput?: string;
|
|
17
20
|
};
|
|
18
21
|
|
|
19
22
|
type ClackPrompts = typeof import("@clack/prompts");
|
|
@@ -93,17 +96,31 @@ function modelChoiceFromAi(ai?: SupportedAi): "codex" | "gemini" | "auto-detect"
|
|
|
93
96
|
return "auto-detect";
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
function defaultAuthorName(authorInput?: string): string {
|
|
100
|
+
const explicit = (authorInput ?? "").trim();
|
|
101
|
+
if (explicit.length > 0) return explicit;
|
|
102
|
+
try {
|
|
103
|
+
const username = os.userInfo().username.trim();
|
|
104
|
+
if (username.length > 0) return username;
|
|
105
|
+
} catch {
|
|
106
|
+
// ignore lookup errors and continue with fallback
|
|
107
|
+
}
|
|
108
|
+
return "Product Author";
|
|
109
|
+
}
|
|
110
|
+
|
|
96
111
|
export async function gatherInitSelections(options: GatherInitUiOptions): Promise<InitSelections> {
|
|
97
112
|
const clack = await loadClack();
|
|
98
113
|
const defaultLang = normalizeLang(options.langInput);
|
|
99
114
|
const fallbackScript: "sh" | "ps" = process.platform === "win32" ? "ps" : "sh";
|
|
100
115
|
const parsedAi = resolveAi(options.aiInput);
|
|
116
|
+
const defaultAuthor = defaultAuthorName(options.authorInput);
|
|
101
117
|
|
|
102
118
|
if (!isInteractiveTerminal()) {
|
|
103
119
|
return {
|
|
104
120
|
ai: parsedAi,
|
|
105
121
|
script: fallbackScript,
|
|
106
122
|
lang: defaultLang,
|
|
123
|
+
author: defaultAuthor,
|
|
107
124
|
interactive: false
|
|
108
125
|
};
|
|
109
126
|
}
|
|
@@ -153,6 +170,16 @@ export async function gatherInitSelections(options: GatherInitUiOptions): Promis
|
|
|
153
170
|
throw new UserError("Initialization cancelled.");
|
|
154
171
|
}
|
|
155
172
|
|
|
173
|
+
const author = await clack.text({
|
|
174
|
+
message: "Author name",
|
|
175
|
+
placeholder: "Shahmarasy",
|
|
176
|
+
defaultValue: defaultAuthor
|
|
177
|
+
});
|
|
178
|
+
if (clack.isCancel(author)) {
|
|
179
|
+
clack.cancel("Initialization cancelled.");
|
|
180
|
+
throw new UserError("Initialization cancelled.");
|
|
181
|
+
}
|
|
182
|
+
|
|
156
183
|
let selectedAi: SupportedAi | undefined;
|
|
157
184
|
if (model === "codex") selectedAi = "codex";
|
|
158
185
|
else if (model === "gemini") selectedAi = "gemini-cli";
|
|
@@ -162,6 +189,7 @@ export async function gatherInitSelections(options: GatherInitUiOptions): Promis
|
|
|
162
189
|
ai: selectedAi,
|
|
163
190
|
script: fallbackScript,
|
|
164
191
|
lang,
|
|
192
|
+
author: String(author).trim() || defaultAuthor,
|
|
165
193
|
interactive: true
|
|
166
194
|
};
|
|
167
195
|
}
|
|
@@ -171,9 +199,10 @@ export function finishInitInteractive(summary: {
|
|
|
171
199
|
settingsPath: string;
|
|
172
200
|
ai?: SupportedAi;
|
|
173
201
|
lang: "tr" | "en";
|
|
202
|
+
author: string;
|
|
174
203
|
}): Promise<void> {
|
|
175
204
|
const aiText = summary.ai ?? "none";
|
|
176
205
|
return loadClack().then((clack) => clack.outro(
|
|
177
|
-
`Scaffold complete.\nAI: ${aiText}\nLanguage: ${summary.lang}\nSettings: ${summary.settingsPath}\nNext: edit brief.md`
|
|
206
|
+
`Scaffold complete.\nAI: ${aiText}\nLanguage: ${summary.lang}\nAuthor: ${summary.author}\nSettings: ${summary.settingsPath}\nNext: edit brief.md`
|
|
178
207
|
));
|
|
179
208
|
}
|
package/src/init.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { briefPath, outputDirPath, outputIndexPath, prodoPath } from "./paths";
|
|
|
9
9
|
import { applyConfiguredPresets } from "./preset-loader";
|
|
10
10
|
import { syncRegistry } from "./registry";
|
|
11
11
|
import { writeSettings } from "./settings";
|
|
12
|
+
import { extractRequiredHeadingsFromTemplate } from "./template-resolver";
|
|
12
13
|
import { buildWorkflowCommands } from "./workflow-commands";
|
|
13
14
|
import {
|
|
14
15
|
NORMALIZED_BRIEF_TEMPLATE,
|
|
@@ -283,7 +284,7 @@ function summarizeParity(items: AssetManifestItem[]): ScaffoldManifest["parity_s
|
|
|
283
284
|
|
|
284
285
|
export async function runInit(
|
|
285
286
|
cwd: string,
|
|
286
|
-
options?: { ai?: SupportedAi; lang?: string; preset?: string; script?: "sh" | "ps" }
|
|
287
|
+
options?: { ai?: SupportedAi; lang?: string; author?: string; preset?: string; script?: "sh" | "ps" }
|
|
287
288
|
): Promise<{ installedAgentFiles: string[]; settingsPath: string }> {
|
|
288
289
|
const root = prodoPath(cwd);
|
|
289
290
|
const artifactDefs = await listArtifactDefinitions(cwd);
|
|
@@ -325,15 +326,20 @@ export async function runInit(
|
|
|
325
326
|
const scriptType = options?.script ?? (process.platform === "win32" ? "ps" : "sh");
|
|
326
327
|
await fs.writeFile(
|
|
327
328
|
path.join(root, "init-options.json"),
|
|
328
|
-
`${JSON.stringify({ ai: options?.ai ?? null, lang: options?.lang ?? "en", preset: options?.preset ?? null, script: scriptType }, null, 2)}\n`,
|
|
329
|
+
`${JSON.stringify({ ai: options?.ai ?? null, lang: options?.lang ?? "en", author: options?.author ?? null, preset: options?.preset ?? null, script: scriptType }, null, 2)}\n`,
|
|
329
330
|
"utf8"
|
|
330
331
|
);
|
|
331
332
|
|
|
332
333
|
await copyDirIfMissing(path.join(projectScaffoldTemplates, "artifacts"), path.join(root, "templates"), copiedAssets);
|
|
333
334
|
for (const artifact of artifactDefs) {
|
|
335
|
+
const markdownTemplatePath = path.join(root, "templates", `${artifact.name}.md`);
|
|
336
|
+
const templateHeadings =
|
|
337
|
+
(await fileExists(markdownTemplatePath))
|
|
338
|
+
? extractRequiredHeadingsFromTemplate(await fs.readFile(markdownTemplatePath, "utf8"))
|
|
339
|
+
: [];
|
|
334
340
|
const schema = {
|
|
335
341
|
...schemaTemplate(artifact.name),
|
|
336
|
-
x_required_headings: artifact.required_headings
|
|
342
|
+
x_required_headings: templateHeadings.length > 0 ? templateHeadings : artifact.required_headings
|
|
337
343
|
};
|
|
338
344
|
await writeFileIfMissing(path.join(root, "schemas", `${artifact.name}.yaml`), yaml.dump(schema));
|
|
339
345
|
await writeFileIfMissing(path.join(root, "prompts", `${artifact.name}.md`), `${promptTemplate(artifact.name, options?.lang ?? "en")}\n`);
|
|
@@ -385,7 +391,8 @@ export async function runInit(
|
|
|
385
391
|
await syncRegistry(cwd);
|
|
386
392
|
const settingsPath = await writeSettings(cwd, {
|
|
387
393
|
lang: (options?.lang ?? "en").trim() || "en",
|
|
388
|
-
ai: options?.ai
|
|
394
|
+
ai: options?.ai,
|
|
395
|
+
author: (options?.author ?? "").trim() || undefined
|
|
389
396
|
});
|
|
390
397
|
return { installedAgentFiles, settingsPath };
|
|
391
398
|
}
|
package/src/normalize.ts
CHANGED
|
@@ -17,6 +17,53 @@ type NormalizeOptions = {
|
|
|
17
17
|
out?: string;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
function normalizedKey(value: string): string {
|
|
21
|
+
return value
|
|
22
|
+
.normalize("NFD")
|
|
23
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
24
|
+
.replace(/ı/g, "i")
|
|
25
|
+
.replace(/İ/g, "I")
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
28
|
+
.trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function extractBriefProductName(rawBrief: string): string | undefined {
|
|
32
|
+
const lines = rawBrief.split(/\r?\n/);
|
|
33
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
34
|
+
const headingMatch = lines[index].match(/^\s*#{1,6}\s+(.+?)\s*$/);
|
|
35
|
+
if (!headingMatch) continue;
|
|
36
|
+
const headingKey = normalizedKey(headingMatch[1]);
|
|
37
|
+
const isProductHeading =
|
|
38
|
+
headingKey === "product name" ||
|
|
39
|
+
headingKey === "project name" ||
|
|
40
|
+
headingKey === "urun adi" ||
|
|
41
|
+
headingKey === "urun ismi";
|
|
42
|
+
if (!isProductHeading) continue;
|
|
43
|
+
|
|
44
|
+
for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
|
|
45
|
+
const rawLine = lines[cursor].trim();
|
|
46
|
+
if (!rawLine) continue;
|
|
47
|
+
if (/^\s*#{1,6}\s+/.test(rawLine)) break;
|
|
48
|
+
const cleaned = rawLine.replace(/^\s*[-*]\s*/, "").trim();
|
|
49
|
+
if (cleaned.length > 0) return cleaned;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function preserveOriginalProductName(
|
|
56
|
+
parsed: Record<string, unknown>,
|
|
57
|
+
rawBrief: string
|
|
58
|
+
): Record<string, unknown> {
|
|
59
|
+
const briefProductName = extractBriefProductName(rawBrief);
|
|
60
|
+
if (!briefProductName) return parsed;
|
|
61
|
+
const generated = typeof parsed.product_name === "string" ? parsed.product_name : "";
|
|
62
|
+
if (!generated.trim()) return { ...parsed, product_name: briefProductName };
|
|
63
|
+
if (normalizedKey(generated) !== normalizedKey(briefProductName)) return parsed;
|
|
64
|
+
return { ...parsed, product_name: briefProductName };
|
|
65
|
+
}
|
|
66
|
+
|
|
20
67
|
function extractJsonObject(raw: string): Record<string, unknown> {
|
|
21
68
|
const trimmed = raw.trim();
|
|
22
69
|
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
@@ -61,17 +108,18 @@ export async function runNormalize(options: NormalizeOptions): Promise<string> {
|
|
|
61
108
|
);
|
|
62
109
|
|
|
63
110
|
const parsed = extractJsonObject(generated.body);
|
|
111
|
+
const preserved = preserveOriginalProductName(parsed, rawBrief);
|
|
64
112
|
const withContracts = {
|
|
65
|
-
...
|
|
113
|
+
...preserved,
|
|
66
114
|
contracts:
|
|
67
|
-
|
|
115
|
+
preserved.contracts ??
|
|
68
116
|
buildContractsFromArrays({
|
|
69
|
-
goals: Array.isArray(
|
|
70
|
-
core_features: Array.isArray(
|
|
71
|
-
?
|
|
117
|
+
goals: Array.isArray(preserved.goals) ? preserved.goals.filter((x): x is string => typeof x === "string") : [],
|
|
118
|
+
core_features: Array.isArray(preserved.core_features)
|
|
119
|
+
? preserved.core_features.filter((x): x is string => typeof x === "string")
|
|
72
120
|
: [],
|
|
73
|
-
constraints: Array.isArray(
|
|
74
|
-
?
|
|
121
|
+
constraints: Array.isArray(preserved.constraints)
|
|
122
|
+
? preserved.constraints.filter((x): x is string => typeof x === "string")
|
|
75
123
|
: []
|
|
76
124
|
})
|
|
77
125
|
};
|
|
@@ -37,7 +37,8 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
37
37
|
const system =
|
|
38
38
|
mode === "normalize"
|
|
39
39
|
? `You normalize messy human product briefs into strict JSON.
|
|
40
|
-
Return valid JSON only, no markdown. Include confidence scores (0..1) for critical fields
|
|
40
|
+
Return valid JSON only, no markdown. Include confidence scores (0..1) for critical fields.
|
|
41
|
+
Preserve source language and Unicode characters exactly; never transliterate Turkish letters to ASCII.`
|
|
41
42
|
: mode === "semantic_consistency"
|
|
42
43
|
? `You detect semantic inconsistencies between paired artifacts.
|
|
43
44
|
Return valid JSON only: { "issues": [{level, code, check, contract_id, file, message, suggestion}] }.`
|
package/src/settings.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { fileExists } from "./utils";
|
|
|
5
5
|
export type ProdoSettings = {
|
|
6
6
|
lang: string;
|
|
7
7
|
ai?: string;
|
|
8
|
+
author?: string;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
const DEFAULT_SETTINGS: ProdoSettings = {
|
|
@@ -19,7 +20,8 @@ export async function readSettings(cwd: string): Promise<ProdoSettings> {
|
|
|
19
20
|
const parsed = JSON.parse(raw) as Partial<ProdoSettings>;
|
|
20
21
|
return {
|
|
21
22
|
lang: typeof parsed.lang === "string" && parsed.lang.trim() ? parsed.lang.trim() : "en",
|
|
22
|
-
ai: typeof parsed.ai === "string" && parsed.ai.trim() ? parsed.ai.trim() : undefined
|
|
23
|
+
ai: typeof parsed.ai === "string" && parsed.ai.trim() ? parsed.ai.trim() : undefined,
|
|
24
|
+
author: typeof parsed.author === "string" && parsed.author.trim() ? parsed.author.trim() : undefined
|
|
23
25
|
};
|
|
24
26
|
} catch {
|
|
25
27
|
return { ...DEFAULT_SETTINGS };
|
|
@@ -31,4 +33,3 @@ export async function writeSettings(cwd: string, settings: ProdoSettings): Promi
|
|
|
31
33
|
await fs.writeFile(path, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
32
34
|
return path;
|
|
33
35
|
}
|
|
34
|
-
|
package/src/templates.ts
CHANGED
|
@@ -347,12 +347,21 @@ Return JSON object with keys:
|
|
|
347
347
|
Rules:
|
|
348
348
|
- do NOT invent missing critical content
|
|
349
349
|
- keep wording concise and concrete
|
|
350
|
+
- preserve original language and Unicode characters exactly from brief
|
|
351
|
+
- never transliterate Turkish letters (ç, ğ, ı, İ, ö, ş, ü) into ASCII
|
|
350
352
|
- if critical field is missing, return empty and low confidence (<0.7)
|
|
351
353
|
- assign deterministic IDs: goals => G1..Gn, features => F1..Fn, constraints => C1..Cn
|
|
352
354
|
- input files are read-only; never modify, summarize, or rewrite \`brief.md\` in-place
|
|
353
355
|
- write normalized output as a new JSON object only`;
|
|
354
356
|
|
|
355
357
|
export function commandTemplate(command: WorkflowCommand): string {
|
|
358
|
+
const normalizeJsonGuard =
|
|
359
|
+
command.cliSubcommand === "normalize"
|
|
360
|
+
? `
|
|
361
|
+
- Normalize output format check:
|
|
362
|
+
- \`.prodo/briefs/normalized-brief.json\` must be strict JSON object (no markdown fences).
|
|
363
|
+
- If invalid, rewrite file as pure JSON object only.`
|
|
364
|
+
: "";
|
|
356
365
|
return `---
|
|
357
366
|
description: ${command.description}
|
|
358
367
|
handoffs:
|
|
@@ -375,6 +384,8 @@ Execution policy:
|
|
|
375
384
|
- Never run \`prodo-${command.cliSubcommand}\`, \`prodo ${command.cliSubcommand}\`, or \`prodo ...\` in shell.
|
|
376
385
|
- Do not inspect hooks or internals unless command execution fails.
|
|
377
386
|
- Input files are read-only; never modify or rewrite \`brief.md\`.
|
|
387
|
+
- Never print full artifact content in chat.
|
|
388
|
+
- Write/update files first, then reply with short status + written file path(s).
|
|
378
389
|
|
|
379
390
|
## Execution
|
|
380
391
|
|
|
@@ -392,6 +403,7 @@ Execution policy:
|
|
|
392
403
|
- Confirm command success state (exit code or validation status).
|
|
393
404
|
- Confirm \`brief.md\` hash/content did not change.
|
|
394
405
|
- Do NOT create manual fallback files under \`.prodo/artifact\` or any ad-hoc folder.
|
|
406
|
+
${normalizeJsonGuard}
|
|
395
407
|
|
|
396
408
|
4. Diagnose only on failure:
|
|
397
409
|
- Inspect \`.prodo/hooks.yml\` only after execution failure.
|
package/src/utils.ts
CHANGED
|
@@ -23,6 +23,20 @@ export function timestampSlug(date = new Date()): string {
|
|
|
23
23
|
return date.toISOString().replace(/[:.]/g, "-");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function pad2(value: number): string {
|
|
27
|
+
return String(value).padStart(2, "0");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function artifactFileStamp(date = new Date()): string {
|
|
31
|
+
const year = date.getFullYear();
|
|
32
|
+
const month = pad2(date.getMonth() + 1);
|
|
33
|
+
const day = pad2(date.getDate());
|
|
34
|
+
const hour = pad2(date.getHours());
|
|
35
|
+
const minute = pad2(date.getMinutes());
|
|
36
|
+
const second = pad2(date.getSeconds());
|
|
37
|
+
return `${year}${month}${day}-${hour}${minute}${second}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
export async function listFilesSortedByMtime(dirPath: string): Promise<string[]> {
|
|
27
41
|
const exists = await fileExists(dirPath);
|
|
28
42
|
if (!exists) return [];
|
package/src/validator.ts
CHANGED
|
@@ -59,10 +59,6 @@ export async function validateSchema(
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const sections = sectionTextMap(doc.body);
|
|
62
|
-
const trMode = String((doc.frontmatter as Record<string, unknown>).language ?? "").toLowerCase().startsWith("tr");
|
|
63
|
-
if (trMode) {
|
|
64
|
-
return { issues, requiredHeadings: [] };
|
|
65
|
-
}
|
|
66
62
|
for (const heading of requiredHeadings) {
|
|
67
63
|
if (!doc.body.includes(heading)) {
|
|
68
64
|
issues.push({
|
package/src/workflow-commands.ts
CHANGED
|
@@ -11,7 +11,8 @@ const BASE_WORKFLOW_COMMANDS: WorkflowCommand[] = [
|
|
|
11
11
|
{ name: "prodo-wireframe", cliSubcommand: "wireframe", description: "Generate wireframe artifact." },
|
|
12
12
|
{ name: "prodo-stories", cliSubcommand: "stories", description: "Generate stories artifact." },
|
|
13
13
|
{ name: "prodo-techspec", cliSubcommand: "techspec", description: "Generate technical specification artifact." },
|
|
14
|
-
{ name: "prodo-validate", cliSubcommand: "validate", description: "Run schema and cross-artifact consistency validation." }
|
|
14
|
+
{ name: "prodo-validate", cliSubcommand: "validate", description: "Run schema and cross-artifact consistency validation." },
|
|
15
|
+
{ name: "prodo-fix", cliSubcommand: "fix", description: "Auto-fix artifacts based on validation report and brief." }
|
|
15
16
|
];
|
|
16
17
|
|
|
17
18
|
export const WORKFLOW_COMMANDS: WorkflowCommand[] = BASE_WORKFLOW_COMMANDS;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Fix failing artifacts using latest validation report and brief-aligned regeneration.
|
|
3
|
+
agent-role: "Recovery Engineer"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## User Input
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
$ARGUMENTS
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Execution Policy
|
|
13
|
+
|
|
14
|
+
- Execute-first, diagnose-second.
|
|
15
|
+
- Do not run shell commands or CLI commands from inside the agent.
|
|
16
|
+
- Never invoke `prodo-fix`, `prodo fix`, or any `prodo ...` command in shell.
|
|
17
|
+
- Input files are read-only; never modify `brief.md`.
|
|
18
|
+
- Write outputs only under `product-docs/` and `.prodo/`.
|
|
19
|
+
- Do not print full artifact bodies in chat.
|
|
20
|
+
|
|
21
|
+
## Execution
|
|
22
|
+
|
|
23
|
+
1. Minimum prerequisites only:
|
|
24
|
+
- `.prodo/` exists
|
|
25
|
+
- `brief.md` exists
|
|
26
|
+
- `.prodo/briefs/normalized-brief.json` exists (refresh if stale)
|
|
27
|
+
- Validation report exists in `product-docs/reports/`
|
|
28
|
+
|
|
29
|
+
2. Execute fix process immediately:
|
|
30
|
+
- Read latest validation report.
|
|
31
|
+
- Identify failing artifact types and regenerate only impacted chain.
|
|
32
|
+
- Use normalized brief + active templates for regeneration.
|
|
33
|
+
|
|
34
|
+
3. Verify result:
|
|
35
|
+
- Re-run validation.
|
|
36
|
+
- Confirm report status is PASS.
|
|
37
|
+
- Confirm `brief.md` remained unchanged.
|
|
38
|
+
|
|
39
|
+
4. Diagnose only on failure:
|
|
40
|
+
- Inspect internal hooks/scripts only after fix attempt fails.
|
|
41
|
+
- Return concise root cause + next repair action.
|
|
42
|
+
|
|
43
|
+
## Handoff
|
|
44
|
+
|
|
45
|
+
If pass: suggest next command `/prodo-validate`.
|
|
46
|
+
If fail: suggest highest-priority artifact command to regenerate manually.
|
|
@@ -1,24 +1,126 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: >
|
|
3
|
+
Transform product brief into normalized, standardized JSON schema for downstream artifact generation.
|
|
4
|
+
Validates structure, enriches metadata, and ensures semantic consistency across all product definitions.
|
|
5
|
+
agent-role: "Data Processor & Validator"
|
|
6
|
+
agent-profile: |
|
|
7
|
+
**Character**: Meticulous Data Architect
|
|
8
|
+
- **Personality**: Precise, methodical, quality-focused
|
|
9
|
+
- **Specialization**: Schema validation, data normalization, integrity auditing
|
|
10
|
+
- **Decision Style**: Rule-based, deterministic (no guessing)
|
|
11
|
+
- **Tolerance**: Zero tolerance for data corruption or inconsistency
|
|
12
|
+
|
|
13
|
+
agent-skills: |
|
|
14
|
+
✓ **Core Skills**:
|
|
15
|
+
- JSON schema validation & transformation
|
|
16
|
+
- Data normalization & standardization
|
|
17
|
+
- Metadata enrichment (timestamps, hashing, versioning)
|
|
18
|
+
- Deterministic output generation
|
|
19
|
+
- Integrity verification & audit trail
|
|
20
|
+
|
|
21
|
+
✓ **Performance Metrics**:
|
|
22
|
+
- Speed: Fast (one-pass processing)
|
|
23
|
+
- Accuracy: 100% deterministic
|
|
24
|
+
- Safety: Immutable input protection
|
|
25
|
+
- Reliability: No side effects
|
|
26
|
+
|
|
27
|
+
✓ **Problem-Solving Approach**:
|
|
28
|
+
- Fail-fast on validation errors
|
|
29
|
+
- Detect corruption early
|
|
30
|
+
- Auto-correct when possible
|
|
31
|
+
- Provide clear diagnostics on failure
|
|
32
|
+
|
|
33
|
+
agent-decision-strategy: |
|
|
34
|
+
**Decision Tree**:
|
|
35
|
+
1. Prerequisites valid? → Continue | Fail-fast
|
|
36
|
+
2. Input corrupted? → Report error | Auto-correct if safe
|
|
37
|
+
3. Output format invalid? → Rewrite as pure JSON | Report conversion
|
|
38
|
+
4. Integrity compromised? → Report issue with remediation | Block output
|
|
39
|
+
|
|
40
|
+
**When to Escalate**:
|
|
41
|
+
- Input file missing or unreadable → User must provide
|
|
42
|
+
- Normalization logic ambiguous → Recommend upstream clarification
|
|
43
|
+
- Multiple corruption patterns detected → Suggest expert review
|
|
44
|
+
|
|
45
|
+
agent-efficiency-tips: |
|
|
46
|
+
⚡ **For Maximum Efficiency**:
|
|
47
|
+
- Run FIRST in pipeline (all others depend on this)
|
|
48
|
+
- Cache results: normalized-brief.json rarely changes
|
|
49
|
+
- Single pass: read once, validate, write
|
|
50
|
+
- No re-processing: output is deterministic
|
|
51
|
+
- Early validation: catch brief issues immediately
|
|
3
52
|
---
|
|
4
53
|
|
|
5
|
-
##
|
|
54
|
+
## Context
|
|
55
|
+
|
|
56
|
+
**Purpose**: Convert unstructured or semi-structured product brief into a machine-readable normalized format.
|
|
57
|
+
|
|
58
|
+
**Upstream Dependencies**: `brief.md` must exist in project root.
|
|
59
|
+
|
|
60
|
+
**Downstream Impact**: All artifact generation commands (PRD, stories, techspec, workflow, wireframe) depend on normalized-brief.json for consistency.
|
|
61
|
+
|
|
62
|
+
**User Input**
|
|
6
63
|
|
|
7
64
|
```text
|
|
8
65
|
$ARGUMENTS
|
|
9
66
|
```
|
|
10
67
|
|
|
11
|
-
Execution
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
68
|
+
## Execution Policy
|
|
69
|
+
|
|
70
|
+
**Safety & Integrity**:
|
|
71
|
+
- Execute-first, diagnose-second (fail fast on validation errors).
|
|
72
|
+
- Do not execute shell/CLI commands from inside the agent.
|
|
73
|
+
- Never invoke `prodo-normalize`, `prodo normalize`, or `prodo ...` commands recursively in shell.
|
|
74
|
+
- **Input files are read-only**: Never modify, rewrite, or transform `brief.md`. Treat it as immutable source of truth.
|
|
75
|
+
- Never print full normalized JSON to chat; return only status + file path.
|
|
76
|
+
|
|
77
|
+
**Output Quality**:
|
|
78
|
+
- Write `.prodo/briefs/normalized-brief.json` with validated strict JSON format.
|
|
79
|
+
- Normalized output must be deterministic (same input always produces identical output).
|
|
80
|
+
- Include metadata: normalized timestamp, schema version, input hash for audit trail.
|
|
81
|
+
|
|
82
|
+
## Execution Steps
|
|
83
|
+
|
|
84
|
+
1. **Verify Minimal Prerequisites**
|
|
85
|
+
- Confirm `.prodo/` directory exists (initialized by `prodo init`).
|
|
86
|
+
- Confirm `brief.md` exists and is readable.
|
|
87
|
+
- Check no corrupt `.prodo/briefs/` state exists.
|
|
88
|
+
|
|
89
|
+
2. **Parse and Normalize `brief.md`**
|
|
90
|
+
- Read `brief.md` without modification.
|
|
91
|
+
- Extract and validate core product attributes (name, description, goals, target audience, scope, constraints).
|
|
92
|
+
- Normalize field types: trim whitespace, standardize arrays, convert dates to ISO-8601.
|
|
93
|
+
- Enrich with derived metadata (section count, complexity score, validation flags).
|
|
94
|
+
|
|
95
|
+
3. **Validate Normalized Format**
|
|
96
|
+
- Confirm `.prodo/briefs/normalized-brief.json` was created.
|
|
97
|
+
- Verify strict JSON compliance:
|
|
98
|
+
- First non-space character must be `{`.
|
|
99
|
+
- No markdown fences (no \`\`\`json or \`\`\`).
|
|
100
|
+
- File must parse successfully as a valid JSON object.
|
|
101
|
+
- No trailing commas, unquoted keys, or comments.
|
|
102
|
+
- If format is invalid, rewrite file as pure JSON object only (auto-correct).
|
|
103
|
+
|
|
104
|
+
4. **Audit & Verify Integrity**
|
|
105
|
+
- Confirm original `brief.md` was not modified (compare timestamps or file hash).
|
|
106
|
+
- Verify normalized output contains no executable code or shell injections.
|
|
107
|
+
- Log schema version and normalized metadata.
|
|
108
|
+
|
|
109
|
+
5. **Safety Constraints**
|
|
110
|
+
- Do not create manual fallback files under `.prodo/` outside expected outputs.
|
|
111
|
+
- Do not write to `.prodo/templates/`, `.prodo/config/`, or other reserved directories.
|
|
112
|
+
- Do not modify or create `.gitignore` or hidden system files.
|
|
113
|
+
|
|
114
|
+
6. **Diagnosis & Error Handling**
|
|
115
|
+
- If `brief.md` is missing: report clear error with recovery instructions.
|
|
116
|
+
- If `brief.md` syntax is invalid: report line numbers and invalid fields.
|
|
117
|
+
- If normalization fails: include schema mismatch details and sample valid structure.
|
|
118
|
+
- If `.prodo/briefs/` cannot be written: report file system permissions issue.
|
|
16
119
|
|
|
17
|
-
##
|
|
120
|
+
## Success Criteria
|
|
18
121
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
6. Diagnose internals only if command fails.
|
|
122
|
+
- ✅ `.prodo/briefs/normalized-brief.json` exists and is valid JSON.
|
|
123
|
+
- ✅ Original `brief.md` remains unchanged.
|
|
124
|
+
- ✅ Normalized file includes schema version and timestamp.
|
|
125
|
+
- ✅ All required product attributes are present and validated.
|
|
126
|
+
- ✅ Ready for downstream artifact generation.
|