@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.
Files changed (45) hide show
  1. package/dist/agents.js +4 -2
  2. package/dist/artifacts.d.ts +1 -0
  3. package/dist/artifacts.js +265 -31
  4. package/dist/cli.js +80 -3
  5. package/dist/init-tui.d.ts +3 -0
  6. package/dist/init-tui.js +28 -1
  7. package/dist/init.d.ts +1 -0
  8. package/dist/init.js +9 -3
  9. package/dist/normalize.js +55 -7
  10. package/dist/providers/openai-provider.js +2 -1
  11. package/dist/settings.d.ts +1 -0
  12. package/dist/settings.js +2 -1
  13. package/dist/templates.d.ts +1 -1
  14. package/dist/templates.js +11 -0
  15. package/dist/utils.d.ts +1 -0
  16. package/dist/utils.js +13 -0
  17. package/dist/validator.js +0 -4
  18. package/dist/workflow-commands.js +2 -1
  19. package/package.json +1 -1
  20. package/presets/fintech/preset.json +48 -1
  21. package/presets/fintech/prompts/prd.md +99 -2
  22. package/presets/marketplace/preset.json +51 -1
  23. package/presets/marketplace/prompts/prd.md +140 -2
  24. package/presets/saas/preset.json +53 -1
  25. package/presets/saas/prompts/prd.md +150 -2
  26. package/src/agents.ts +4 -2
  27. package/src/artifacts.ts +323 -28
  28. package/src/cli.ts +97 -6
  29. package/src/init-tui.ts +30 -1
  30. package/src/init.ts +11 -4
  31. package/src/normalize.ts +55 -7
  32. package/src/providers/openai-provider.ts +2 -1
  33. package/src/settings.ts +3 -2
  34. package/src/templates.ts +12 -0
  35. package/src/utils.ts +14 -0
  36. package/src/validator.ts +0 -4
  37. package/src/workflow-commands.ts +2 -1
  38. package/templates/commands/prodo-fix.md +46 -0
  39. package/templates/commands/prodo-normalize.md +116 -14
  40. package/templates/commands/prodo-prd.md +136 -12
  41. package/templates/commands/prodo-stories.md +151 -12
  42. package/templates/commands/prodo-techspec.md +165 -12
  43. package/templates/commands/prodo-validate.md +184 -23
  44. package/templates/commands/prodo-wireframe.md +186 -12
  45. 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
- ...parsed,
113
+ ...preserved,
66
114
  contracts:
67
- parsed.contracts ??
115
+ preserved.contracts ??
68
116
  buildContractsFromArrays({
69
- goals: Array.isArray(parsed.goals) ? parsed.goals.filter((x): x is string => typeof x === "string") : [],
70
- core_features: Array.isArray(parsed.core_features)
71
- ? parsed.core_features.filter((x): x is string => typeof x === "string")
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(parsed.constraints)
74
- ? parsed.constraints.filter((x): x is string => typeof x === "string")
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({
@@ -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: Normalize product brief.
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
- ## User Input
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 policy:
12
- - Execute-first, diagnose-second.
13
- - Do not run shell/CLI commands from inside the agent.
14
- - Never run `prodo-normalize`, `prodo normalize`, or `prodo ...` in shell.
15
- - Input files are read-only; never modify or rewrite `brief.md`.
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
- ## Execution
120
+ ## Success Criteria
18
121
 
19
- 1. Verify minimal prerequisites (`.prodo/`, `brief.md`).
20
- 2. Read only `brief.md` and normalize it into `.prodo/briefs/normalized-brief.json`.
21
- 3. Confirm `.prodo/briefs/normalized-brief.json` exists.
22
- 4. Confirm `brief.md` content did not change.
23
- 5. Do not create any manual files under `.prodo/` except expected runtime outputs.
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.