@shahmarasy/prodo 0.1.0 → 0.1.1

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,19 @@ 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
71
  ${promptBody}
84
-
85
- Script options:
86
- ${scriptsBlock}
87
72
  """`;
88
73
  }
89
74
  function toSkill(name, body, frontmatter) {
@@ -151,7 +136,7 @@ async function installAgentCommands(projectRoot, ai) {
151
136
  }
152
137
  const outPath = node_path_1.default.join(target, `${commandName}${cfg.extension}`);
153
138
  const replacedBody = parsed.body.replaceAll("$ARGUMENTS", cfg.argsPlaceholder);
154
- await promises_1.default.writeFile(outPath, `${renderFrontmatter(parsed.frontmatter)}\n${replacedBody}`, "utf8");
139
+ await promises_1.default.writeFile(outPath, `${renderFrontmatter(sanitizeFrontmatter(parsed.frontmatter))}\n${replacedBody}`, "utf8");
155
140
  written.push(outPath);
156
141
  }
157
142
  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
@@ -75,7 +75,44 @@ function extractTurkishTitle(featureText) {
75
75
  return "Ekran";
76
76
  return base;
77
77
  }
78
- async function resolvePrompt(cwd, artifactType, templateContent, requiredHeadings, agent) {
78
+ function replaceTemplateTokens(template, replacements, fallbackFromToken) {
79
+ let out = template;
80
+ for (const [key, value] of Object.entries(replacements)) {
81
+ out = out.replace(new RegExp(`\\{\\{\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\}\\}`, "g"), value);
82
+ }
83
+ return out.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_match, tokenRaw) => {
84
+ const token = String(tokenRaw).trim();
85
+ return fallbackFromToken(token);
86
+ });
87
+ }
88
+ function renderWorkflowMermaidTemplate(templateContent, normalized, coverage, lang) {
89
+ const tr = lang.toLowerCase().startsWith("tr");
90
+ const primaryFeatureId = coverage.core_features[0] ?? normalized.contracts.core_features[0]?.id ?? "F1";
91
+ const primaryFeatureText = normalized.contracts.core_features.find((item) => item.id === primaryFeatureId)?.text ??
92
+ normalized.contracts.core_features[0]?.text ??
93
+ (tr ? "Kullanici islemi" : "User action");
94
+ return replaceTemplateTokens(templateContent, {
95
+ "Flow Name": tr ? "Ana Akis" : "Main Flow",
96
+ "Primary Actor": normalized.audience[0] ?? (tr ? "Kullanici" : "User"),
97
+ "Primary Action": `[${primaryFeatureId}] ${primaryFeatureText}`,
98
+ "Success State": tr ? "Basari" : "Success",
99
+ "Error State": tr ? "Hata" : "Error"
100
+ }, (token) => {
101
+ const key = token.toLowerCase();
102
+ if (key.includes("actor") || key.includes("user"))
103
+ return normalized.audience[0] ?? (tr ? "Kullanici" : "User");
104
+ if (key.includes("action") || key.includes("feature"))
105
+ return `[${primaryFeatureId}] ${primaryFeatureText}`;
106
+ if (key.includes("success"))
107
+ return tr ? "Basari" : "Success";
108
+ if (key.includes("error") || key.includes("fail"))
109
+ return tr ? "Hata" : "Error";
110
+ if (key.includes("flow"))
111
+ return tr ? "Ana Akis" : "Main Flow";
112
+ return token;
113
+ });
114
+ }
115
+ async function resolvePrompt(cwd, artifactType, templateContent, requiredHeadings, companionTemplate, agent) {
79
116
  const base = await promises_1.default.readFile((0, paths_1.promptPath)(cwd, artifactType), "utf8");
80
117
  const authority = `Template authority (STRICT):
81
118
  - Treat this template as the single output structure source.
@@ -89,6 +126,14 @@ Resolved template:
89
126
  \`\`\`md
90
127
  ${templateContent.trim()}
91
128
  \`\`\``;
129
+ const companionAuthority = companionTemplate
130
+ ? `Native companion template (STRICT reference):
131
+ - Path: ${companionTemplate.path}
132
+ - Preserve this native format and structure when generating companion artifact.
133
+ \`\`\`${artifactType === "workflow" ? "mermaid" : "html"}
134
+ ${companionTemplate.content.trim()}
135
+ \`\`\``
136
+ : "";
92
137
  const workflowPairing = artifactType === "workflow"
93
138
  ? `
94
139
  Workflow paired output contract (STRICT):
@@ -100,9 +145,19 @@ flowchart TD
100
145
  \`\`\`
101
146
  - Mermaid block is mandatory.`
102
147
  : "";
148
+ const wireframePairing = artifactType === "wireframe"
149
+ ? `
150
+ Wireframe paired output contract (STRICT):
151
+ - Output markdown explanation first (template headings).
152
+ - Generate companion HTML screens based on native wireframe template.
153
+ - HTML must stay low-fidelity and structure-first.`
154
+ : "";
103
155
  const withTemplate = `${base}
104
156
 
105
- ${authority}${workflowPairing}`;
157
+ ${authority}
158
+ ${companionAuthority}
159
+ ${workflowPairing}
160
+ ${wireframePairing}`;
106
161
  if (!agent)
107
162
  return withTemplate;
108
163
  return `${withTemplate}
@@ -255,7 +310,7 @@ function splitWorkflowPair(raw) {
255
310
  }
256
311
  return { markdown, mermaid };
257
312
  }
258
- async function writeWireframeScreens(targetDir, baseName, normalized, coverage, lang, headings) {
313
+ async function writeWireframeScreens(targetDir, baseName, normalized, coverage, lang, headings, htmlTemplateContent) {
259
314
  const tr = lang.toLowerCase().startsWith("tr");
260
315
  const screenContracts = normalized.contracts.core_features
261
316
  .filter((item) => coverage.core_features.includes(item.id))
@@ -268,7 +323,7 @@ async function writeWireframeScreens(targetDir, baseName, normalized, coverage,
268
323
  const screenBase = `${baseName}-${index + 1}-${toSlug(title)}`;
269
324
  const htmlPath = node_path_1.default.join(targetDir, `${screenBase}.html`);
270
325
  const mdPath = node_path_1.default.join(targetDir, `${screenBase}.md`);
271
- const html = `<!doctype html>
326
+ const fallbackHtml = `<!doctype html>
272
327
  <html lang="${lang}">
273
328
  <head>
274
329
  <meta charset="utf-8" />
@@ -300,8 +355,38 @@ async function writeWireframeScreens(targetDir, baseName, normalized, coverage,
300
355
  </section>
301
356
  </main>
302
357
  </body>
303
- </html>
304
- `;
358
+ </html>`;
359
+ const htmlTemplate = htmlTemplateContent && htmlTemplateContent.trim().length > 0 ? htmlTemplateContent : fallbackHtml;
360
+ const html = replaceTemplateTokens(htmlTemplate, {
361
+ "Screen Title": title,
362
+ "Primary Action": tr ? "Kaydet" : "Save",
363
+ "Description Label": tr ? "Aciklama" : "Description",
364
+ "Description Placeholder": `[${screen.id}] ${screen.text}`,
365
+ "Meta Label 1": tr ? "Kontrat" : "Contract",
366
+ "Meta Value 1": screen.id,
367
+ "Meta Label 2": tr ? "Aktor" : "Actor",
368
+ "Meta Value 2": normalized.audience[0] ?? (tr ? "Kullanici" : "User"),
369
+ "Field Label": tr ? "Alan" : "Field",
370
+ "Detailed Input Area": tr ? "Detayli Giris Alani" : "Detailed Input Area",
371
+ "Upload / Attachment Area": tr ? "Dosya Alani" : "Upload Area",
372
+ "Allowed file types / notes": tr ? "Dusuk sadakatli wireframe." : "Low-fidelity wireframe.",
373
+ "Consent / confirmation text": tr ? "Onay metni" : "Confirmation text"
374
+ }, (token) => {
375
+ const key = token.toLowerCase();
376
+ if (key.includes("screen") || key.includes("title"))
377
+ return title;
378
+ if (key.includes("action") || key.includes("button"))
379
+ return tr ? "Kaydet" : "Save";
380
+ if (key.includes("field"))
381
+ return tr ? "Alan" : "Field";
382
+ if (key.includes("description") || key.includes("summary"))
383
+ return `[${screen.id}] ${screen.text}`;
384
+ if (key.includes("actor") || key.includes("user"))
385
+ return normalized.audience[0] ?? (tr ? "Kullanici" : "User");
386
+ if (key.includes("logo"))
387
+ return "[ LOGO ]";
388
+ return token;
389
+ });
305
390
  await promises_1.default.writeFile(htmlPath, html, "utf8");
306
391
  const defaultMap = {
307
392
  purpose: [`- [${screen.id}] ${screen.text}`],
@@ -371,9 +456,16 @@ async function generateArtifact(options) {
371
456
  const normalizedBriefRaw = await (0, utils_1.readJsonFile)(normalizedPath);
372
457
  const normalizedBrief = (0, normalized_brief_1.parseNormalizedBriefOrThrow)(normalizedBriefRaw);
373
458
  const template = await (0, template_resolver_1.resolveTemplate)({ cwd, artifactType });
459
+ const companionTemplate = await (0, template_resolver_1.resolveCompanionTemplate)({ cwd, artifactType });
374
460
  if (!template || template.content.trim().length === 0) {
375
461
  throw new errors_1.UserError(`Missing ${artifactType} template. Create \`.prodo/templates/${artifactType}.md\` before running \`prodo-${artifactType}\`.`);
376
462
  }
463
+ if (artifactType === "workflow" && !companionTemplate) {
464
+ throw new errors_1.UserError("Missing workflow companion template. Create `.prodo/templates/workflow.mmd` before running `prodo-workflow`.");
465
+ }
466
+ if (artifactType === "wireframe" && !companionTemplate) {
467
+ throw new errors_1.UserError("Missing wireframe companion template. Create `.prodo/templates/wireframe.html` before running `prodo-wireframe`.");
468
+ }
377
469
  const templateHeadings = template && template.content.trim().length > 0 ? (0, template_resolver_1.extractRequiredHeadingsFromTemplate)(template.content) : [];
378
470
  if (templateHeadings.length === 0) {
379
471
  throw new errors_1.UserError(`${artifactType} template has no extractable headings. Add markdown headings to \`${template.path}\`.`);
@@ -381,7 +473,7 @@ async function generateArtifact(options) {
381
473
  const computedHeadings = templateHeadings.length > 0
382
474
  ? templateHeadings
383
475
  : (def.required_headings.length > 0 ? def.required_headings : (0, constants_1.defaultRequiredHeadings)(artifactType));
384
- const prompt = await resolvePrompt(cwd, artifactType, template?.content ?? "", computedHeadings, agent);
476
+ const prompt = await resolvePrompt(cwd, artifactType, template?.content ?? "", computedHeadings, companionTemplate, agent);
385
477
  const provider = (0, providers_1.createProvider)();
386
478
  const upstreamArtifacts = await buildUpstreamArtifacts(cwd, artifactType, def.upstream);
387
479
  const schemaHint = {
@@ -395,6 +487,8 @@ async function generateArtifact(options) {
395
487
  contractCatalog: normalizedBrief.contracts,
396
488
  templateContent: template?.content ?? "",
397
489
  templatePath: template?.path ?? "",
490
+ companionTemplateContent: companionTemplate?.content ?? "",
491
+ companionTemplatePath: companionTemplate?.path ?? "",
398
492
  outputLanguage: settings.lang
399
493
  }, schemaHint);
400
494
  let generatedBody = generated.body.trim();
@@ -421,6 +515,9 @@ async function generateArtifact(options) {
421
515
  };
422
516
  }
423
517
  }
518
+ if (artifactType === "workflow" && companionTemplate?.content) {
519
+ workflowMermaidBody = renderWorkflowMermaidTemplate(companionTemplate.content, normalizedBrief, contractCoverage, settings.lang).trim();
520
+ }
424
521
  enforceLanguage(generatedBody, settings.lang, artifactType);
425
522
  const uncovered = missingCoverage(def.required_contracts, normalizedBrief, contractCoverage);
426
523
  if (uncovered.length > 0) {
@@ -477,7 +574,7 @@ async function generateArtifact(options) {
477
574
  }
478
575
  else if (artifactType === "wireframe") {
479
576
  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);
577
+ const wireframe = await writeWireframeScreens(node_path_1.default.dirname(finalPath), base, normalizedBrief, contractCoverage, settings.lang, schemaHint.requiredHeadings, companionTemplate?.content ?? null);
481
578
  doc = {
482
579
  frontmatter: doc.frontmatter,
483
580
  body: wireframe.summaryBody
package/dist/cli.js CHANGED
@@ -139,15 +139,13 @@ async function runCli(options = {}) {
139
139
  if (selectedAi) {
140
140
  out(`Agent command set installed for ${selectedAi}.`);
141
141
  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.");
142
+ out("Agent workflow: edit brief.md, then run slash commands in your agent.");
144
143
  }
145
144
  else {
146
145
  out("No agent selected. Use `prodo generate` for end-to-end generation.");
147
- out("Advanced commands are still available when needed.");
148
146
  }
149
147
  out(`Settings file: ${result.settingsPath}`);
150
- out("Next: edit brief.md, then run `prodo generate`.");
148
+ out("Next: edit brief.md.");
151
149
  });
152
150
  program
153
151
  .command("generate")
@@ -182,7 +180,7 @@ async function runCli(options = {}) {
182
180
  });
183
181
  });
184
182
  program
185
- .command("normalize")
183
+ .command("normalize", { hidden: true })
186
184
  .description("Advanced: normalize brief without full pipeline")
187
185
  .option("--brief <path>", "path to start brief markdown")
188
186
  .option("--out <path>", "output normalized brief json path")
@@ -210,7 +208,7 @@ async function runCli(options = {}) {
210
208
  });
211
209
  for (const type of artifactTypes) {
212
210
  program
213
- .command(type)
211
+ .command(type, { hidden: true })
214
212
  .description(`Advanced: generate only ${type} artifact`)
215
213
  .option("--from <path>", "path to normalized-brief.json")
216
214
  .option("--out <path>", "output file path")
@@ -222,7 +220,7 @@ async function runCli(options = {}) {
222
220
  });
223
221
  }
224
222
  program
225
- .command("agent-commands")
223
+ .command("agent-commands", { hidden: true })
226
224
  .requiredOption("--agent <name>", "agent profile: codex | gemini-cli | claude-cli")
227
225
  .action(async (opts) => {
228
226
  const agent = (0, agents_1.resolveAgent)(opts.agent);
@@ -246,7 +244,7 @@ async function runCli(options = {}) {
246
244
  }
247
245
  });
248
246
  program
249
- .command("validate")
247
+ .command("validate", { hidden: true })
250
248
  .description("Advanced: run validation only")
251
249
  .option("--strict", "treat warnings as errors")
252
250
  .option("--report <path>", "report output path")
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
  }
@@ -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,7 @@ $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.
367
365
  - Do not inspect hooks or internals unless command execution fails.
368
366
  - Input files are read-only; never modify or rewrite \`brief.md\`.
369
367
 
@@ -375,12 +373,14 @@ Execution policy:
375
373
  - Confirm output location is writable.
376
374
 
377
375
  2. Execute immediately:
378
- - Execute the \`${command.cliSubcommand}\` process through Prodo internal runtime.
376
+ - Execute the \`${command.cliSubcommand}\` process directly using workspace files and Prodo rules.
379
377
  - Keep narration short and action-oriented.
380
378
 
381
379
  3. Verify result:
382
- - Confirm expected output file(s) were created/updated.
380
+ - Confirm expected output file(s) were created/updated under \`product-docs/\` (or \`.prodo/briefs\` for normalize).
383
381
  - Confirm command success state (exit code or validation status).
382
+ - Confirm \`brief.md\` hash/content did not change.
383
+ - Do NOT create manual fallback files under \`.prodo/artifact\` or any ad-hoc folder.
384
384
 
385
385
  4. Diagnose only on failure:
386
386
  - 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.1",
4
4
  "description": "CLI-first, prompt-powered product artifact kit",
5
5
  "main": "dist/cli.js",
6
6
  "types": "dist/cli.d.ts",
@@ -64,35 +64,20 @@ function renderFrontmatter(frontmatter: Record<string, unknown>): string {
64
64
  return `---\n${yaml.dump(frontmatter)}---\n`;
65
65
  }
66
66
 
67
- function resolveScriptBlock(frontmatter: Record<string, unknown>, argsPlaceholder: string): string {
68
- const run = frontmatter.run as Record<string, unknown> | undefined;
69
- const runAction = typeof run?.action === "string" ? run.action.trim() : "";
70
- const runMode = typeof run?.mode === "string" ? run.mode.trim() : "";
71
- if (runAction) {
72
- const modeSuffix = runMode ? ` (${runMode})` : "";
73
- return `- Internal action: ${runAction}${modeSuffix}`;
74
- }
75
- const runCommand = typeof run?.command === "string" ? run.command.replace("{ARGS}", argsPlaceholder) : "";
76
- if (runCommand) {
77
- return `- Command: ${runCommand}`;
78
- }
79
- const scripts = frontmatter.scripts as Record<string, unknown> | undefined;
80
- const sh = typeof scripts?.sh === "string" ? scripts.sh.replace("{ARGS}", argsPlaceholder) : "";
81
- const ps = typeof scripts?.ps === "string" ? scripts.ps.replace("{ARGS}", argsPlaceholder) : "";
82
- return [`- Bash: ${sh}`, `- PowerShell: ${ps}`].filter((line) => line.length > 8).join("\n");
67
+ function sanitizeFrontmatter(frontmatter: Record<string, unknown>): Record<string, unknown> {
68
+ const out = { ...frontmatter };
69
+ delete out.run;
70
+ delete out.scripts;
71
+ return out;
83
72
  }
84
73
 
85
74
  function toTomlPrompt(body: string, frontmatter: Record<string, unknown>, argsPlaceholder: string): string {
86
75
  const description = String(frontmatter.description ?? "Prodo command");
87
- const scriptsBlock = resolveScriptBlock(frontmatter, argsPlaceholder);
88
76
  const promptBody = body.replaceAll("$ARGUMENTS", argsPlaceholder);
89
77
  return `description = "${description.replace(/"/g, '\\"')}"
90
78
 
91
79
  prompt = """
92
80
  ${promptBody}
93
-
94
- Script options:
95
- ${scriptsBlock}
96
81
  """`;
97
82
  }
98
83
 
@@ -167,7 +152,7 @@ export async function installAgentCommands(projectRoot: string, ai: SupportedAi)
167
152
 
168
153
  const outPath = path.join(target, `${commandName}${cfg.extension}`);
169
154
  const replacedBody = parsed.body.replaceAll("$ARGUMENTS", cfg.argsPlaceholder);
170
- await fs.writeFile(outPath, `${renderFrontmatter(parsed.frontmatter)}\n${replacedBody}`, "utf8");
155
+ await fs.writeFile(outPath, `${renderFrontmatter(sanitizeFrontmatter(parsed.frontmatter))}\n${replacedBody}`, "utf8");
171
156
  written.push(outPath);
172
157
  }
173
158
  return written;
package/src/agents.ts CHANGED
@@ -29,28 +29,28 @@ export type AgentCommandSet = {
29
29
  };
30
30
 
31
31
  export async function loadAgentCommandSet(_cwd: string, agent: AgentId): Promise<AgentCommandSet> {
32
- const prefix = `prodo-${agent}`;
32
+ const prefix = "/prodo";
33
33
  return {
34
34
  agent,
35
35
  description: "Agent-specific command set for Prodo artifact pipeline.",
36
36
  recommended_sequence: [
37
37
  { command: "prodo init . --ai <agent> --lang <en|tr>", purpose: "Initialize Prodo scaffold and agent commands." },
38
- { command: `${prefix} normalize`, purpose: "Normalize start brief into normalized brief JSON." },
39
- { command: `${prefix} prd`, purpose: "Generate PRD artifact." },
40
- { command: `${prefix} workflow`, purpose: "Generate workflow artifact." },
41
- { command: `${prefix} wireframe`, purpose: "Generate wireframe artifact." },
42
- { command: `${prefix} stories`, purpose: "Generate stories artifact." },
43
- { command: `${prefix} techspec`, purpose: "Generate techspec artifact." },
44
- { command: `${prefix} validate`, purpose: "Run validation report." }
38
+ { command: `${prefix}-normalize`, purpose: "Normalize start brief into normalized brief JSON." },
39
+ { command: `${prefix}-prd`, purpose: "Generate PRD artifact." },
40
+ { command: `${prefix}-workflow`, purpose: "Generate workflow artifact." },
41
+ { command: `${prefix}-wireframe`, purpose: "Generate wireframe artifact." },
42
+ { command: `${prefix}-stories`, purpose: "Generate stories artifact." },
43
+ { command: `${prefix}-techspec`, purpose: "Generate techspec artifact." },
44
+ { command: `${prefix}-validate`, purpose: "Run validation report." }
45
45
  ],
46
46
  artifact_shortcuts: {
47
- normalize: `${prefix} normalize`,
48
- prd: `${prefix} prd`,
49
- workflow: `${prefix} workflow`,
50
- wireframe: `${prefix} wireframe`,
51
- stories: `${prefix} stories`,
52
- techspec: `${prefix} techspec`,
53
- validate: `${prefix} validate`
47
+ normalize: `${prefix}-normalize`,
48
+ prd: `${prefix}-prd`,
49
+ workflow: `${prefix}-workflow`,
50
+ wireframe: `${prefix}-wireframe`,
51
+ stories: `${prefix}-stories`,
52
+ techspec: `${prefix}-techspec`,
53
+ validate: `${prefix}-validate`
54
54
  }
55
55
  };
56
56
  }