@shahmarasy/prodo 0.1.0 → 0.1.2

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.
@@ -56,34 +56,24 @@ function renderFrontmatter(frontmatter) {
56
56
  return "";
57
57
  return `---\n${js_yaml_1.default.dump(frontmatter)}---\n`;
58
58
  }
59
- function resolveScriptBlock(frontmatter, argsPlaceholder) {
60
- const run = frontmatter.run;
61
- const runAction = typeof run?.action === "string" ? run.action.trim() : "";
62
- const runMode = typeof run?.mode === "string" ? run.mode.trim() : "";
63
- if (runAction) {
64
- const modeSuffix = runMode ? ` (${runMode})` : "";
65
- return `- Internal action: ${runAction}${modeSuffix}`;
66
- }
67
- const runCommand = typeof run?.command === "string" ? run.command.replace("{ARGS}", argsPlaceholder) : "";
68
- if (runCommand) {
69
- return `- Command: ${runCommand}`;
70
- }
71
- const scripts = frontmatter.scripts;
72
- const sh = typeof scripts?.sh === "string" ? scripts.sh.replace("{ARGS}", argsPlaceholder) : "";
73
- const ps = typeof scripts?.ps === "string" ? scripts.ps.replace("{ARGS}", argsPlaceholder) : "";
74
- return [`- Bash: ${sh}`, `- PowerShell: ${ps}`].filter((line) => line.length > 8).join("\n");
59
+ function sanitizeFrontmatter(frontmatter) {
60
+ const out = { ...frontmatter };
61
+ delete out.run;
62
+ delete out.scripts;
63
+ return out;
75
64
  }
76
65
  function toTomlPrompt(body, frontmatter, argsPlaceholder) {
77
66
  const description = String(frontmatter.description ?? "Prodo command");
78
- const scriptsBlock = resolveScriptBlock(frontmatter, argsPlaceholder);
79
67
  const promptBody = body.replaceAll("$ARGUMENTS", argsPlaceholder);
80
68
  return `description = "${description.replace(/"/g, '\\"')}"
81
69
 
82
70
  prompt = """
83
- ${promptBody}
71
+ Important execution rule:
72
+ - This is an agent slash command, not a shell command.
73
+ - Do NOT run \`prodo-normalize\`, \`prodo-prd\`, or \`prodo ...\` in shell.
74
+ - Execute the workflow directly using workspace files.
84
75
 
85
- Script options:
86
- ${scriptsBlock}
76
+ ${promptBody}
87
77
  """`;
88
78
  }
89
79
  function toSkill(name, body, frontmatter) {
@@ -151,7 +141,7 @@ async function installAgentCommands(projectRoot, ai) {
151
141
  }
152
142
  const outPath = node_path_1.default.join(target, `${commandName}${cfg.extension}`);
153
143
  const replacedBody = parsed.body.replaceAll("$ARGUMENTS", cfg.argsPlaceholder);
154
- await promises_1.default.writeFile(outPath, `${renderFrontmatter(parsed.frontmatter)}\n${replacedBody}`, "utf8");
144
+ await promises_1.default.writeFile(outPath, `${renderFrontmatter(sanitizeFrontmatter(parsed.frontmatter))}\n${replacedBody}`, "utf8");
155
145
  written.push(outPath);
156
146
  }
157
147
  return written;
package/dist/agents.js CHANGED
@@ -20,28 +20,28 @@ function resolveAgent(agent) {
20
20
  return agent;
21
21
  }
22
22
  async function loadAgentCommandSet(_cwd, agent) {
23
- const prefix = `prodo-${agent}`;
23
+ const prefix = "/prodo";
24
24
  return {
25
25
  agent,
26
26
  description: "Agent-specific command set for Prodo artifact pipeline.",
27
27
  recommended_sequence: [
28
28
  { command: "prodo init . --ai <agent> --lang <en|tr>", purpose: "Initialize Prodo scaffold and agent commands." },
29
- { command: `${prefix} normalize`, purpose: "Normalize start brief into normalized brief JSON." },
30
- { command: `${prefix} prd`, purpose: "Generate PRD artifact." },
31
- { command: `${prefix} workflow`, purpose: "Generate workflow artifact." },
32
- { command: `${prefix} wireframe`, purpose: "Generate wireframe artifact." },
33
- { command: `${prefix} stories`, purpose: "Generate stories artifact." },
34
- { command: `${prefix} techspec`, purpose: "Generate techspec artifact." },
35
- { command: `${prefix} validate`, purpose: "Run validation report." }
29
+ { command: `${prefix}-normalize`, purpose: "Normalize start brief into normalized brief JSON." },
30
+ { command: `${prefix}-prd`, purpose: "Generate PRD artifact." },
31
+ { command: `${prefix}-workflow`, purpose: "Generate workflow artifact." },
32
+ { command: `${prefix}-wireframe`, purpose: "Generate wireframe artifact." },
33
+ { command: `${prefix}-stories`, purpose: "Generate stories artifact." },
34
+ { command: `${prefix}-techspec`, purpose: "Generate techspec artifact." },
35
+ { command: `${prefix}-validate`, purpose: "Run validation report." }
36
36
  ],
37
37
  artifact_shortcuts: {
38
- normalize: `${prefix} normalize`,
39
- prd: `${prefix} prd`,
40
- workflow: `${prefix} workflow`,
41
- wireframe: `${prefix} wireframe`,
42
- stories: `${prefix} stories`,
43
- techspec: `${prefix} techspec`,
44
- validate: `${prefix} validate`
38
+ normalize: `${prefix}-normalize`,
39
+ prd: `${prefix}-prd`,
40
+ workflow: `${prefix}-workflow`,
41
+ wireframe: `${prefix}-wireframe`,
42
+ stories: `${prefix}-stories`,
43
+ techspec: `${prefix}-techspec`,
44
+ validate: `${prefix}-validate`
45
45
  }
46
46
  };
47
47
  }
package/dist/artifacts.js CHANGED
@@ -53,18 +53,53 @@ async function loadArtifactDoc(filePath) {
53
53
  body: parsed.content
54
54
  };
55
55
  }
56
+ function languageProbe(body) {
57
+ const stripped = body
58
+ .replace(/```[\s\S]*?```/g, " ")
59
+ .replace(/^\s*#{1,6}\s+.*$/gm, " ")
60
+ .replace(/<[^>]+>/g, " ")
61
+ .replace(/\|/g, " ")
62
+ .replace(/\s+/g, " ")
63
+ .trim()
64
+ .toLowerCase();
65
+ return ` ${stripped} `;
66
+ }
56
67
  function hasEnglishLeak(body) {
57
- const englishMarkers = [" the ", " and ", " with ", " user ", " should ", " must ", " requirement ", " flow "];
58
- const normalized = ` ${body.toLowerCase().replace(/\s+/g, " ")} `;
68
+ const englishMarkers = [" the ", " and ", " with ", " user ", " should ", " must ", " requirement ", " flow ", " error ", " success "];
69
+ const normalized = languageProbe(body);
59
70
  return englishMarkers.filter((m) => normalized.includes(m)).length >= 2;
60
71
  }
72
+ function hasTurkishLeak(body) {
73
+ const turkishMarkers = [
74
+ " ve ",
75
+ " ile ",
76
+ " kullanici ",
77
+ " kullanıcı ",
78
+ " akis ",
79
+ " akış ",
80
+ " hata ",
81
+ " basari ",
82
+ " başarı ",
83
+ " ekran ",
84
+ " islem ",
85
+ " işlem ",
86
+ " gerekli "
87
+ ];
88
+ const normalized = languageProbe(body);
89
+ return turkishMarkers.filter((m) => normalized.includes(m)).length >= 2;
90
+ }
61
91
  function enforceLanguage(body, lang, artifactType) {
62
92
  const normalized = (lang || "en").toLowerCase();
63
- if (!normalized.startsWith("tr"))
64
- return;
65
- if (hasEnglishLeak(body)) {
93
+ if (normalized.startsWith("tr")) {
94
+ if (!hasEnglishLeak(body))
95
+ return;
66
96
  throw new errors_1.UserError(`Language enforcement failed for ${artifactType}: output contains English fragments while language is Turkish.`);
67
97
  }
98
+ if (normalized.startsWith("en")) {
99
+ if (!hasTurkishLeak(body))
100
+ return;
101
+ throw new errors_1.UserError(`Language enforcement failed for ${artifactType}: output contains Turkish fragments while language is English.`);
102
+ }
68
103
  }
69
104
  function toSlug(value) {
70
105
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "screen";
@@ -75,7 +110,44 @@ function extractTurkishTitle(featureText) {
75
110
  return "Ekran";
76
111
  return base;
77
112
  }
78
- async function resolvePrompt(cwd, artifactType, templateContent, requiredHeadings, agent) {
113
+ function replaceTemplateTokens(template, replacements, fallbackFromToken) {
114
+ let out = template;
115
+ for (const [key, value] of Object.entries(replacements)) {
116
+ out = out.replace(new RegExp(`\\{\\{\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\}\\}`, "g"), value);
117
+ }
118
+ return out.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_match, tokenRaw) => {
119
+ const token = String(tokenRaw).trim();
120
+ return fallbackFromToken(token);
121
+ });
122
+ }
123
+ function renderWorkflowMermaidTemplate(templateContent, normalized, coverage, lang) {
124
+ const tr = lang.toLowerCase().startsWith("tr");
125
+ const primaryFeatureId = coverage.core_features[0] ?? normalized.contracts.core_features[0]?.id ?? "F1";
126
+ const primaryFeatureText = normalized.contracts.core_features.find((item) => item.id === primaryFeatureId)?.text ??
127
+ normalized.contracts.core_features[0]?.text ??
128
+ (tr ? "Kullanici islemi" : "User action");
129
+ return replaceTemplateTokens(templateContent, {
130
+ "Flow Name": tr ? "Ana Akis" : "Main Flow",
131
+ "Primary Actor": normalized.audience[0] ?? (tr ? "Kullanici" : "User"),
132
+ "Primary Action": `[${primaryFeatureId}] ${primaryFeatureText}`,
133
+ "Success State": tr ? "Basari" : "Success",
134
+ "Error State": tr ? "Hata" : "Error"
135
+ }, (token) => {
136
+ const key = token.toLowerCase();
137
+ if (key.includes("actor") || key.includes("user"))
138
+ return normalized.audience[0] ?? (tr ? "Kullanici" : "User");
139
+ if (key.includes("action") || key.includes("feature"))
140
+ return `[${primaryFeatureId}] ${primaryFeatureText}`;
141
+ if (key.includes("success"))
142
+ return tr ? "Basari" : "Success";
143
+ if (key.includes("error") || key.includes("fail"))
144
+ return tr ? "Hata" : "Error";
145
+ if (key.includes("flow"))
146
+ return tr ? "Ana Akis" : "Main Flow";
147
+ return token;
148
+ });
149
+ }
150
+ async function resolvePrompt(cwd, artifactType, templateContent, requiredHeadings, companionTemplate, agent) {
79
151
  const base = await promises_1.default.readFile((0, paths_1.promptPath)(cwd, artifactType), "utf8");
80
152
  const authority = `Template authority (STRICT):
81
153
  - Treat this template as the single output structure source.
@@ -89,6 +161,14 @@ Resolved template:
89
161
  \`\`\`md
90
162
  ${templateContent.trim()}
91
163
  \`\`\``;
164
+ const companionAuthority = companionTemplate
165
+ ? `Native companion template (STRICT reference):
166
+ - Path: ${companionTemplate.path}
167
+ - Preserve this native format and structure when generating companion artifact.
168
+ \`\`\`${artifactType === "workflow" ? "mermaid" : "html"}
169
+ ${companionTemplate.content.trim()}
170
+ \`\`\``
171
+ : "";
92
172
  const workflowPairing = artifactType === "workflow"
93
173
  ? `
94
174
  Workflow paired output contract (STRICT):
@@ -100,9 +180,19 @@ flowchart TD
100
180
  \`\`\`
101
181
  - Mermaid block is mandatory.`
102
182
  : "";
183
+ const wireframePairing = artifactType === "wireframe"
184
+ ? `
185
+ Wireframe paired output contract (STRICT):
186
+ - Output markdown explanation first (template headings).
187
+ - Generate companion HTML screens based on native wireframe template.
188
+ - HTML must stay low-fidelity and structure-first.`
189
+ : "";
103
190
  const withTemplate = `${base}
104
191
 
105
- ${authority}${workflowPairing}`;
192
+ ${authority}
193
+ ${companionAuthority}
194
+ ${workflowPairing}
195
+ ${wireframePairing}`;
106
196
  if (!agent)
107
197
  return withTemplate;
108
198
  return `${withTemplate}
@@ -255,7 +345,7 @@ function splitWorkflowPair(raw) {
255
345
  }
256
346
  return { markdown, mermaid };
257
347
  }
258
- async function writeWireframeScreens(targetDir, baseName, normalized, coverage, lang, headings) {
348
+ async function writeWireframeScreens(targetDir, baseName, normalized, coverage, lang, headings, htmlTemplateContent) {
259
349
  const tr = lang.toLowerCase().startsWith("tr");
260
350
  const screenContracts = normalized.contracts.core_features
261
351
  .filter((item) => coverage.core_features.includes(item.id))
@@ -268,7 +358,7 @@ async function writeWireframeScreens(targetDir, baseName, normalized, coverage,
268
358
  const screenBase = `${baseName}-${index + 1}-${toSlug(title)}`;
269
359
  const htmlPath = node_path_1.default.join(targetDir, `${screenBase}.html`);
270
360
  const mdPath = node_path_1.default.join(targetDir, `${screenBase}.md`);
271
- const html = `<!doctype html>
361
+ const fallbackHtml = `<!doctype html>
272
362
  <html lang="${lang}">
273
363
  <head>
274
364
  <meta charset="utf-8" />
@@ -300,8 +390,39 @@ async function writeWireframeScreens(targetDir, baseName, normalized, coverage,
300
390
  </section>
301
391
  </main>
302
392
  </body>
303
- </html>
304
- `;
393
+ </html>`;
394
+ const htmlTemplate = htmlTemplateContent && htmlTemplateContent.trim().length > 0 ? htmlTemplateContent : fallbackHtml;
395
+ const html = replaceTemplateTokens(htmlTemplate, {
396
+ "Screen Title": title,
397
+ "Primary Action": tr ? "Kaydet" : "Save",
398
+ "Description Label": tr ? "Aciklama" : "Description",
399
+ "Description Placeholder": `[${screen.id}] ${screen.text}`,
400
+ "Meta Label 1": tr ? "Kontrat" : "Contract",
401
+ "Meta Value 1": screen.id,
402
+ "Meta Label 2": tr ? "Aktor" : "Actor",
403
+ "Meta Value 2": normalized.audience[0] ?? (tr ? "Kullanici" : "User"),
404
+ "Field Label": tr ? "Alan" : "Field",
405
+ "Detailed Input Area": tr ? "Detayli Giris Alani" : "Detailed Input Area",
406
+ "Upload / Attachment Area": tr ? "Dosya Alani" : "Upload Area",
407
+ "Allowed file types / notes": tr ? "Dusuk sadakatli wireframe." : "Low-fidelity wireframe.",
408
+ "Consent / confirmation text": tr ? "Onay metni" : "Confirmation text"
409
+ }, (token) => {
410
+ const key = token.toLowerCase();
411
+ if (key.includes("screen") || key.includes("title"))
412
+ return title;
413
+ if (key.includes("action") || key.includes("button"))
414
+ return tr ? "Kaydet" : "Save";
415
+ if (key.includes("field"))
416
+ return tr ? "Alan" : "Field";
417
+ if (key.includes("description") || key.includes("summary"))
418
+ return `[${screen.id}] ${screen.text}`;
419
+ if (key.includes("actor") || key.includes("user"))
420
+ return normalized.audience[0] ?? (tr ? "Kullanici" : "User");
421
+ if (key.includes("logo"))
422
+ return "[ LOGO ]";
423
+ return token;
424
+ });
425
+ enforceLanguage(html, lang, "wireframe");
305
426
  await promises_1.default.writeFile(htmlPath, html, "utf8");
306
427
  const defaultMap = {
307
428
  purpose: [`- [${screen.id}] ${screen.text}`],
@@ -352,6 +473,7 @@ async function writeWireframeScreens(targetDir, baseName, normalized, coverage,
352
473
  mdLines.push("");
353
474
  }
354
475
  const mdBody = mdLines.join("\n").trim();
476
+ enforceLanguage(mdBody, lang, "wireframe");
355
477
  await promises_1.default.writeFile(mdPath, `${mdBody}\n`, "utf8");
356
478
  if (!primaryMdPath)
357
479
  primaryMdPath = mdPath;
@@ -371,9 +493,16 @@ async function generateArtifact(options) {
371
493
  const normalizedBriefRaw = await (0, utils_1.readJsonFile)(normalizedPath);
372
494
  const normalizedBrief = (0, normalized_brief_1.parseNormalizedBriefOrThrow)(normalizedBriefRaw);
373
495
  const template = await (0, template_resolver_1.resolveTemplate)({ cwd, artifactType });
496
+ const companionTemplate = await (0, template_resolver_1.resolveCompanionTemplate)({ cwd, artifactType });
374
497
  if (!template || template.content.trim().length === 0) {
375
498
  throw new errors_1.UserError(`Missing ${artifactType} template. Create \`.prodo/templates/${artifactType}.md\` before running \`prodo-${artifactType}\`.`);
376
499
  }
500
+ if (artifactType === "workflow" && !companionTemplate) {
501
+ throw new errors_1.UserError("Missing workflow companion template. Create `.prodo/templates/workflow.mmd` before running `prodo-workflow`.");
502
+ }
503
+ if (artifactType === "wireframe" && !companionTemplate) {
504
+ throw new errors_1.UserError("Missing wireframe companion template. Create `.prodo/templates/wireframe.html` before running `prodo-wireframe`.");
505
+ }
377
506
  const templateHeadings = template && template.content.trim().length > 0 ? (0, template_resolver_1.extractRequiredHeadingsFromTemplate)(template.content) : [];
378
507
  if (templateHeadings.length === 0) {
379
508
  throw new errors_1.UserError(`${artifactType} template has no extractable headings. Add markdown headings to \`${template.path}\`.`);
@@ -381,7 +510,7 @@ async function generateArtifact(options) {
381
510
  const computedHeadings = templateHeadings.length > 0
382
511
  ? templateHeadings
383
512
  : (def.required_headings.length > 0 ? def.required_headings : (0, constants_1.defaultRequiredHeadings)(artifactType));
384
- const prompt = await resolvePrompt(cwd, artifactType, template?.content ?? "", computedHeadings, agent);
513
+ const prompt = await resolvePrompt(cwd, artifactType, template?.content ?? "", computedHeadings, companionTemplate, agent);
385
514
  const provider = (0, providers_1.createProvider)();
386
515
  const upstreamArtifacts = await buildUpstreamArtifacts(cwd, artifactType, def.upstream);
387
516
  const schemaHint = {
@@ -395,6 +524,8 @@ async function generateArtifact(options) {
395
524
  contractCatalog: normalizedBrief.contracts,
396
525
  templateContent: template?.content ?? "",
397
526
  templatePath: template?.path ?? "",
527
+ companionTemplateContent: companionTemplate?.content ?? "",
528
+ companionTemplatePath: companionTemplate?.path ?? "",
398
529
  outputLanguage: settings.lang
399
530
  }, schemaHint);
400
531
  let generatedBody = generated.body.trim();
@@ -421,7 +552,13 @@ async function generateArtifact(options) {
421
552
  };
422
553
  }
423
554
  }
555
+ if (artifactType === "workflow" && companionTemplate?.content) {
556
+ workflowMermaidBody = renderWorkflowMermaidTemplate(companionTemplate.content, normalizedBrief, contractCoverage, settings.lang).trim();
557
+ }
424
558
  enforceLanguage(generatedBody, settings.lang, artifactType);
559
+ if (artifactType === "workflow" && workflowMermaidBody) {
560
+ enforceLanguage(workflowMermaidBody, settings.lang, artifactType);
561
+ }
425
562
  const uncovered = missingCoverage(def.required_contracts, normalizedBrief, contractCoverage);
426
563
  if (uncovered.length > 0) {
427
564
  const lines = uncovered
@@ -477,7 +614,7 @@ async function generateArtifact(options) {
477
614
  }
478
615
  else if (artifactType === "wireframe") {
479
616
  const base = node_path_1.default.parse(finalPath).name;
480
- const wireframe = await writeWireframeScreens(node_path_1.default.dirname(finalPath), base, normalizedBrief, contractCoverage, settings.lang, schemaHint.requiredHeadings);
617
+ const wireframe = await writeWireframeScreens(node_path_1.default.dirname(finalPath), base, normalizedBrief, contractCoverage, settings.lang, schemaHint.requiredHeadings, companionTemplate?.content ?? null);
481
618
  doc = {
482
619
  frontmatter: doc.frontmatter,
483
620
  body: wireframe.summaryBody
package/dist/cli.js CHANGED
@@ -124,7 +124,6 @@ async function runCli(options = {}) {
124
124
  projectRoot,
125
125
  settingsPath: result.settingsPath,
126
126
  ai: selectedAi,
127
- script: selected.script,
128
127
  lang: selected.lang
129
128
  });
130
129
  return;
@@ -139,15 +138,13 @@ async function runCli(options = {}) {
139
138
  if (selectedAi) {
140
139
  out(`Agent command set installed for ${selectedAi}.`);
141
140
  out(`Installed ${result.installedAgentFiles.length} command files.`);
142
- out("Primary workflow: run `prodo generate` after editing brief.md.");
143
- out("Advanced: artifact-level slash commands remain available when needed.");
141
+ out("Agent workflow: edit brief.md, then run slash commands in your agent.");
144
142
  }
145
143
  else {
146
144
  out("No agent selected. Use `prodo generate` for end-to-end generation.");
147
- out("Advanced commands are still available when needed.");
148
145
  }
149
146
  out(`Settings file: ${result.settingsPath}`);
150
- out("Next: edit brief.md, then run `prodo generate`.");
147
+ out("Next: edit brief.md.");
151
148
  });
152
149
  program
153
150
  .command("generate")
@@ -182,7 +179,7 @@ async function runCli(options = {}) {
182
179
  });
183
180
  });
184
181
  program
185
- .command("normalize")
182
+ .command("normalize", { hidden: true })
186
183
  .description("Advanced: normalize brief without full pipeline")
187
184
  .option("--brief <path>", "path to start brief markdown")
188
185
  .option("--out <path>", "output normalized brief json path")
@@ -210,7 +207,7 @@ async function runCli(options = {}) {
210
207
  });
211
208
  for (const type of artifactTypes) {
212
209
  program
213
- .command(type)
210
+ .command(type, { hidden: true })
214
211
  .description(`Advanced: generate only ${type} artifact`)
215
212
  .option("--from <path>", "path to normalized-brief.json")
216
213
  .option("--out <path>", "output file path")
@@ -222,7 +219,7 @@ async function runCli(options = {}) {
222
219
  });
223
220
  }
224
221
  program
225
- .command("agent-commands")
222
+ .command("agent-commands", { hidden: true })
226
223
  .requiredOption("--agent <name>", "agent profile: codex | gemini-cli | claude-cli")
227
224
  .action(async (opts) => {
228
225
  const agent = (0, agents_1.resolveAgent)(opts.agent);
@@ -246,7 +243,7 @@ async function runCli(options = {}) {
246
243
  }
247
244
  });
248
245
  program
249
- .command("validate")
246
+ .command("validate", { hidden: true })
250
247
  .description("Advanced: run validation only")
251
248
  .option("--strict", "treat warnings as errors")
252
249
  .option("--report <path>", "report output path")
@@ -15,7 +15,6 @@ export declare function finishInitInteractive(summary: {
15
15
  projectRoot: string;
16
16
  settingsPath: string;
17
17
  ai?: SupportedAi;
18
- script: "sh" | "ps";
19
18
  lang: "tr" | "en";
20
19
  }): Promise<void>;
21
20
  export {};
package/dist/init-tui.js CHANGED
@@ -117,18 +117,6 @@ async function gatherInitSelections(options) {
117
117
  clack.cancel("Initialization cancelled.");
118
118
  throw new errors_1.UserError("Initialization cancelled.");
119
119
  }
120
- const script = await clack.select({
121
- message: "Select script type",
122
- initialValue: fallbackScript,
123
- options: [
124
- { value: "sh", label: "sh", hint: "Command profile (metadata)" },
125
- { value: "ps", label: "ps", hint: "Command profile (metadata)" }
126
- ]
127
- });
128
- if (clack.isCancel(script)) {
129
- clack.cancel("Initialization cancelled.");
130
- throw new errors_1.UserError("Initialization cancelled.");
131
- }
132
120
  const lang = await clack.select({
133
121
  message: "Select language",
134
122
  initialValue: defaultLang,
@@ -150,12 +138,12 @@ async function gatherInitSelections(options) {
150
138
  selectedAi = detectedAi;
151
139
  return {
152
140
  ai: selectedAi,
153
- script,
141
+ script: fallbackScript,
154
142
  lang,
155
143
  interactive: true
156
144
  };
157
145
  }
158
146
  function finishInitInteractive(summary) {
159
147
  const aiText = summary.ai ?? "none";
160
- return loadClack().then((clack) => clack.outro(`Scaffold complete.\nAI: ${aiText}\nScript: ${summary.script}\nLanguage: ${summary.lang}\nSettings: ${summary.settingsPath}\nNext: edit brief.md`));
148
+ return loadClack().then((clack) => clack.outro(`Scaffold complete.\nAI: ${aiText}\nLanguage: ${summary.lang}\nSettings: ${summary.settingsPath}\nNext: edit brief.md`));
161
149
  }
package/dist/init.js CHANGED
@@ -123,6 +123,26 @@ async function copyDirIfMissing(sourceDir, targetDir, copiedAssets) {
123
123
  });
124
124
  }
125
125
  }
126
+ async function refreshLegacyCommandTemplates(sourceDir, targetDir) {
127
+ if (!(await (0, utils_1.fileExists)(sourceDir)) || !(await (0, utils_1.fileExists)(targetDir)))
128
+ return;
129
+ const entries = await promises_1.default.readdir(sourceDir, { withFileTypes: true });
130
+ for (const entry of entries) {
131
+ if (!entry.isFile())
132
+ continue;
133
+ if (!entry.name.startsWith("prodo-") || !entry.name.endsWith(".md"))
134
+ continue;
135
+ const src = node_path_1.default.join(sourceDir, entry.name);
136
+ const dst = node_path_1.default.join(targetDir, entry.name);
137
+ if (!(await (0, utils_1.fileExists)(dst)))
138
+ continue;
139
+ const existing = await promises_1.default.readFile(dst, "utf8");
140
+ const isLegacyRunMode = /run:\s*\n\s*action:\s*[^\n]+/m.test(existing) || /mode:\s*internal-runtime/m.test(existing);
141
+ if (!isLegacyRunMode)
142
+ continue;
143
+ await promises_1.default.copyFile(src, dst);
144
+ }
145
+ }
126
146
  async function buildAssetManifest(pairs, previous, backup) {
127
147
  const previousByTarget = new Map();
128
148
  for (const item of previous?.assets ?? []) {
@@ -264,6 +284,7 @@ async function runInit(cwd, options) {
264
284
  await writeFileIfMissing(node_path_1.default.join(root, "templates", templateFileName(artifact.name)), `${(0, templates_1.artifactTemplateTemplate)(artifact.name, options?.lang ?? "en")}\n`);
265
285
  }
266
286
  await copyDirIfMissing(node_path_1.default.join(projectScaffoldTemplates, "commands"), node_path_1.default.join(root, "commands"), copiedAssets);
287
+ await refreshLegacyCommandTemplates(node_path_1.default.join(projectScaffoldTemplates, "commands"), node_path_1.default.join(root, "commands"));
267
288
  for (const command of workflowCommands) {
268
289
  await writeFileIfMissing(node_path_1.default.join(root, "commands", `${command.name}.md`), `${(0, templates_1.commandTemplate)(command)}\n`);
269
290
  }
@@ -107,8 +107,15 @@ function buildArtifactBody(schemaHint, inputContext) {
107
107
  const lang = typeof inputContext.outputLanguage === "string" ? inputContext.outputLanguage.toLowerCase() : "en";
108
108
  const items = normalizeSectionItems(inputContext);
109
109
  const coverage = coverageItems(schemaHint, inputContext);
110
+ const localizedItems = lang === "tr" ? items.map((_, index) => `Gereksinim maddesi ${index + 1}`) : items;
111
+ const localizedCoverage = lang === "tr"
112
+ ? coverage.map((item, index) => ({
113
+ id: item.id,
114
+ text: `Kontrat kapsami ${index + 1}`
115
+ }))
116
+ : coverage;
110
117
  const fallback = lang === "tr" ? "Detay daha sonra netlestirilecek." : "To be refined.";
111
- const sections = schemaHint.requiredHeadings.map((heading) => headingBlock(heading, items, fallback, coverage));
118
+ const sections = schemaHint.requiredHeadings.map((heading) => headingBlock(heading, localizedItems, fallback, localizedCoverage));
112
119
  const title = lang === "tr"
113
120
  ? `# ${productName} icin ${schemaHint.artifactType.toUpperCase()}`
114
121
  : `# ${schemaHint.artifactType.toUpperCase()} for ${productName}`;
@@ -7,5 +7,9 @@ export declare function resolveTemplate(options: ResolveOptions): Promise<{
7
7
  path: string;
8
8
  content: string;
9
9
  } | null>;
10
+ export declare function resolveCompanionTemplate(options: ResolveOptions): Promise<{
11
+ path: string;
12
+ content: string;
13
+ } | null>;
10
14
  export declare function extractRequiredHeadingsFromTemplate(content: string): string[];
11
15
  export {};
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.resolveTemplate = resolveTemplate;
7
+ exports.resolveCompanionTemplate = resolveCompanionTemplate;
7
8
  exports.extractRequiredHeadingsFromTemplate = extractRequiredHeadingsFromTemplate;
8
9
  const promises_1 = __importDefault(require("node:fs/promises"));
9
10
  const paths_1 = require("./paths");
@@ -23,6 +24,23 @@ async function resolveTemplate(options) {
23
24
  }
24
25
  return null;
25
26
  }
27
+ async function resolveCompanionTemplate(options) {
28
+ const { cwd, artifactType } = options;
29
+ const nativeExt = artifactType === "workflow" ? ".mmd" : artifactType === "wireframe" ? ".html" : null;
30
+ if (!nativeExt)
31
+ return null;
32
+ const candidates = [
33
+ ...(0, paths_1.overrideTemplateCandidatePaths)(cwd, artifactType),
34
+ ...(0, paths_1.templateCandidatePaths)(cwd, artifactType)
35
+ ].filter((candidate) => candidate.toLowerCase().endsWith(nativeExt));
36
+ for (const filePath of candidates) {
37
+ if (await (0, utils_1.fileExists)(filePath)) {
38
+ const content = await promises_1.default.readFile(filePath, "utf8");
39
+ return { path: filePath, content };
40
+ }
41
+ }
42
+ return null;
43
+ }
26
44
  function extractRequiredHeadingsFromTemplate(content) {
27
45
  return (0, markdown_1.extractRequiredHeadings)(content);
28
46
  }
package/dist/templates.js CHANGED
@@ -350,9 +350,6 @@ handoffs:
350
350
  agent: prodo-next
351
351
  prompt: Continue with the next Prodo command in sequence.
352
352
  send: true
353
- run:
354
- action: ${command.cliSubcommand}
355
- mode: internal-runtime
356
353
  ---
357
354
 
358
355
  ## User Input
@@ -364,6 +361,8 @@ $ARGUMENTS
364
361
  Execution policy:
365
362
  - Execute-first, diagnose-second.
366
363
  - Perform only minimal prerequisite checks before execution.
364
+ - Do not run shell commands or CLI commands from inside the agent.
365
+ - Never run \`prodo-${command.cliSubcommand}\`, \`prodo ${command.cliSubcommand}\`, or \`prodo ...\` in shell.
367
366
  - Do not inspect hooks or internals unless command execution fails.
368
367
  - Input files are read-only; never modify or rewrite \`brief.md\`.
369
368
 
@@ -375,12 +374,14 @@ Execution policy:
375
374
  - Confirm output location is writable.
376
375
 
377
376
  2. Execute immediately:
378
- - Execute the \`${command.cliSubcommand}\` process through Prodo internal runtime.
377
+ - Execute the \`${command.cliSubcommand}\` process directly using workspace files and Prodo rules.
379
378
  - Keep narration short and action-oriented.
380
379
 
381
380
  3. Verify result:
382
- - Confirm expected output file(s) were created/updated.
381
+ - Confirm expected output file(s) were created/updated under \`product-docs/\` (or \`.prodo/briefs\` for normalize).
383
382
  - Confirm command success state (exit code or validation status).
383
+ - Confirm \`brief.md\` hash/content did not change.
384
+ - Do NOT create manual fallback files under \`.prodo/artifact\` or any ad-hoc folder.
384
385
 
385
386
  4. Diagnose only on failure:
386
387
  - Inspect \`.prodo/hooks.yml\` only after execution failure.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shahmarasy/prodo",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CLI-first, prompt-powered product artifact kit",
5
5
  "main": "dist/cli.js",
6
6
  "types": "dist/cli.d.ts",