@shahmarasy/prodo 0.1.5 → 0.1.6

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 (67) hide show
  1. package/dist/agents/system-prompts.js +12 -12
  2. package/dist/cli/agent-command-installer.js +18 -18
  3. package/dist/cli/doctor.js +2 -2
  4. package/dist/cli/index.js +32 -30
  5. package/dist/cli/init-tui.d.ts +2 -2
  6. package/dist/cli/init-tui.js +43 -36
  7. package/dist/cli/init.d.ts +1 -0
  8. package/dist/cli/init.js +2 -1
  9. package/dist/core/artifacts.js +72 -72
  10. package/dist/core/settings.d.ts +1 -0
  11. package/dist/core/settings.js +10 -2
  12. package/dist/core/templates.js +248 -248
  13. package/dist/i18n/en.json +45 -45
  14. package/dist/i18n/tr.json +45 -45
  15. package/dist/providers/mock-provider.js +5 -5
  16. package/dist/providers/openai-provider.js +12 -12
  17. package/dist/skill-engine/context.d.ts +7 -0
  18. package/dist/skill-engine/context.js +76 -0
  19. package/dist/skill-engine/discovery.d.ts +2 -0
  20. package/dist/skill-engine/discovery.js +52 -0
  21. package/dist/skill-engine/graph.d.ts +4 -0
  22. package/dist/skill-engine/graph.js +114 -0
  23. package/dist/skill-engine/index.d.ts +11 -0
  24. package/dist/skill-engine/index.js +49 -0
  25. package/dist/skill-engine/pipeline.d.ts +9 -0
  26. package/dist/skill-engine/pipeline.js +84 -0
  27. package/dist/skill-engine/registry.d.ts +12 -0
  28. package/dist/skill-engine/registry.js +74 -0
  29. package/dist/skill-engine/types.d.ts +66 -0
  30. package/dist/skill-engine/types.js +2 -0
  31. package/dist/skill-engine/validator.d.ts +4 -0
  32. package/dist/skill-engine/validator.js +90 -0
  33. package/dist/skills/fix.d.ts +2 -0
  34. package/dist/skills/fix.js +41 -0
  35. package/dist/skills/generate-artifact.d.ts +2 -0
  36. package/dist/skills/generate-artifact.js +42 -0
  37. package/dist/skills/normalize.d.ts +2 -0
  38. package/dist/skills/normalize.js +29 -0
  39. package/dist/skills/register-core.d.ts +2 -0
  40. package/dist/skills/register-core.js +21 -0
  41. package/dist/skills/validate.d.ts +2 -0
  42. package/dist/skills/validate.js +37 -0
  43. package/package.json +4 -6
  44. package/src/cli/doctor.ts +2 -2
  45. package/src/cli/index.ts +35 -31
  46. package/src/cli/init-tui.ts +220 -208
  47. package/src/cli/init.ts +3 -2
  48. package/src/core/settings.ts +41 -35
  49. package/src/skill-engine/context.ts +90 -0
  50. package/src/skill-engine/discovery.ts +57 -0
  51. package/src/skill-engine/graph.ts +136 -0
  52. package/src/skill-engine/index.ts +55 -0
  53. package/src/skill-engine/pipeline.ts +112 -0
  54. package/src/skill-engine/registry.ts +75 -0
  55. package/src/skill-engine/types.ts +81 -0
  56. package/src/skill-engine/validator.ts +135 -0
  57. package/src/skills/fix.ts +45 -0
  58. package/src/skills/generate-artifact.ts +48 -0
  59. package/src/skills/{normalize-skill.ts → normalize.ts} +15 -12
  60. package/src/skills/register-core.ts +27 -0
  61. package/src/skills/validate.ts +40 -0
  62. package/src/skills/engine.ts +0 -94
  63. package/src/skills/fix-skill.ts +0 -38
  64. package/src/skills/generate-artifact-skill.ts +0 -32
  65. package/src/skills/generate-pipeline-skill.ts +0 -49
  66. package/src/skills/types.ts +0 -36
  67. package/src/skills/validate-skill.ts +0 -29
@@ -5,26 +5,26 @@ exports.buildUserMessage = buildUserMessage;
5
5
  function buildSystemPrompt(schemaHint, outputLanguage) {
6
6
  const mode = schemaHint.artifactType;
7
7
  if (mode === "normalize") {
8
- return `You normalize messy human product briefs into strict JSON.
9
- Return valid JSON only, no markdown. Include confidence scores (0..1) for critical fields.
8
+ return `You normalize messy human product briefs into strict JSON.
9
+ Return valid JSON only, no markdown. Include confidence scores (0..1) for critical fields.
10
10
  Preserve source language and Unicode characters exactly; never transliterate Turkish letters to ASCII.`;
11
11
  }
12
12
  if (mode === "semantic_consistency") {
13
- return `You detect semantic inconsistencies between paired artifacts.
13
+ return `You detect semantic inconsistencies between paired artifacts.
14
14
  Return valid JSON only: { "issues": [{level, code, check, contract_id, file, message, suggestion}] }.`;
15
15
  }
16
16
  if (mode === "contract_relevance") {
17
- return `You verify whether tagged content actually matches the referenced contract text.
17
+ return `You verify whether tagged content actually matches the referenced contract text.
18
18
  Return valid JSON only: { "relevant": boolean, "score": number, "reason": string }.`;
19
19
  }
20
- return `You are a product-document generator.
21
- Return only Markdown body content.
22
- Headings required:
23
- ${schemaHint.requiredHeadings.join("\n")}
24
- Required contract tags:
25
- ${schemaHint.requiredContracts.join(", ")}
26
- Use tags like [G1], [F2], [C1] where relevant.
27
- Output language: ${outputLanguage}
20
+ return `You are a product-document generator.
21
+ Return only Markdown body content.
22
+ Headings required:
23
+ ${schemaHint.requiredHeadings.join("\n")}
24
+ Required contract tags:
25
+ ${schemaHint.requiredContracts.join(", ")}
26
+ Use tags like [G1], [F2], [C1] where relevant.
27
+ Output language: ${outputLanguage}
28
28
  Do not translate required headings.`;
29
29
  }
30
30
  function buildUserMessage(prompt, inputContext) {
@@ -65,28 +65,28 @@ function sanitizeFrontmatter(frontmatter) {
65
65
  function toTomlPrompt(body, frontmatter, argsPlaceholder) {
66
66
  const description = String(frontmatter.description ?? "Prodo command");
67
67
  const promptBody = body.replaceAll("$ARGUMENTS", argsPlaceholder);
68
- return `description = "${description.replace(/"/g, '\\"')}"
69
-
70
- prompt = """
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.
75
-
76
- ${promptBody}
68
+ return `description = "${description.replace(/"/g, '\\"')}"
69
+
70
+ prompt = """
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.
75
+
76
+ ${promptBody}
77
77
  """`;
78
78
  }
79
79
  function toSkill(name, body, frontmatter) {
80
80
  const description = String(frontmatter.description ?? "Prodo workflow command");
81
- return `---
82
- name: ${name}
83
- description: ${description}
84
- compatibility: Requires Prodo project scaffold (.prodo)
85
- metadata:
86
- author: prodo
87
- source: .prodo/commands/${name}.md
88
- ---
89
-
81
+ return `---
82
+ name: ${name}
83
+ description: ${description}
84
+ compatibility: Requires Prodo project scaffold (.prodo)
85
+ metadata:
86
+ author: prodo
87
+ source: .prodo/commands/${name}.md
88
+ ---
89
+
90
90
  ${body}`;
91
91
  }
92
92
  function resolveAi(ai) {
@@ -120,8 +120,8 @@ async function runDoctor(cwd, out) {
120
120
  out("");
121
121
  out(renderLogo(width));
122
122
  out("");
123
- out(center(color("Prodo — Product Artifact Toolkit", "\u001B[1;37m"), width));
124
- out(center(color("Crafted by Codex, guided by Shahmarasy intelligence", "\u001B[2;37m"), width));
123
+ out(center(color("Prodo — AI-Powered Product Owner", "\u001B[1;37m"), width));
124
+ out(center(color("Built by Shahmarasy · Works with Claude, Codex, and Gemini", "\u001B[2;37m"), width));
125
125
  out("");
126
126
  out("Checking environment...");
127
127
  out("");
package/dist/cli/index.js CHANGED
@@ -138,7 +138,7 @@ async function runCli(options = {}) {
138
138
  const artifactTypes = await (0, artifact_registry_1.listArtifactTypes)(cwd);
139
139
  program
140
140
  .command("init [target]")
141
- .option("--ai <name>", "agent integration: codex | gemini-cli | claude-cli")
141
+ .option("--ai <name>", "agent: codex | gemini-cli | claude-cli")
142
142
  .option("--lang <code>", "document language (e.g. en, tr)")
143
143
  .option("--author <name>", "document author name")
144
144
  .option("--preset <name>", "preset to install during initialization")
@@ -181,16 +181,15 @@ async function runCli(options = {}) {
181
181
  });
182
182
  out(`Initialized Prodo scaffold at ${node_path_1.default.join(projectRoot, ".prodo")}`);
183
183
  if (selectedAi) {
184
- out(`Agent command set installed for ${selectedAi}.`);
184
+ const label = selectedAi === "claude-cli" ? "Claude Code"
185
+ : selectedAi === "codex" ? "Codex" : "Gemini CLI";
186
+ out(`Agent commands installed for ${label}.`);
185
187
  out(`Installed ${result.installedAgentFiles.length} command files.`);
186
- out("Agent workflow: edit brief.md, then run slash commands in your agent.");
188
+ out(`Next: edit brief.md, open in ${label}, run /prodo-normalize`);
187
189
  }
188
190
  else {
189
- out("No agent selected. Use `prodo generate` for end-to-end generation.");
191
+ out("Next: edit brief.md, then run `prodo generate`.");
190
192
  }
191
- out(`Settings file: ${result.settingsPath}`);
192
- out(`Author: ${selected.author}`);
193
- out("Next: edit brief.md.");
194
193
  });
195
194
  program
196
195
  .command("generate")
@@ -213,26 +212,26 @@ async function runCli(options = {}) {
213
212
  return;
214
213
  }
215
214
  await withBriefReadOnlyGuard(cwd, async () => {
215
+ const { createEngine, createPipelineState } = await Promise.resolve().then(() => __importStar(require("../skill-engine")));
216
+ const engine = await createEngine(cwd, out);
217
+ const state = createPipelineState(cwd);
218
+ const pipelineSkills = ["normalize", ...artifactTypes, "validate"];
216
219
  await (0, hook_executor_1.runHookPhase)(cwd, "before_normalize", out);
217
- const normalizedPath = await (0, normalize_1.runNormalize)({ cwd });
218
- out(`Normalized brief written to: ${normalizedPath}`);
219
- await (0, hook_executor_1.runHookPhase)(cwd, "after_normalize", out);
220
- for (const type of artifactTypes) {
221
- await runArtifactCommand(type, { from: normalizedPath, agent: opts.agent }, cwd, out, {
222
- suggestValidate: false
223
- });
224
- }
225
- await (0, hook_executor_1.runHookPhase)(cwd, "before_validate", out);
226
- const result = await (0, validate_1.runValidate)(cwd, {
227
- strict: Boolean(opts.strict),
228
- report: opts.report
220
+ const finalState = await engine.runPipeline(pipelineSkills, state, {
221
+ log: (msg) => {
222
+ out(msg);
223
+ },
224
+ agent: opts.agent
229
225
  });
230
- out(`Validation report written to: ${result.reportPath}`);
231
- if (!result.pass) {
226
+ await (0, hook_executor_1.runHookPhase)(cwd, "after_validate", out);
227
+ if (finalState.validationResult && !finalState.validationResult.pass) {
228
+ out(`Validation report written to: ${finalState.validationResult.reportPath}`);
232
229
  throw new errors_1.UserError("Validation failed. Review report and fix issues.");
233
230
  }
231
+ if (finalState.validationResult) {
232
+ out(`Validation report written to: ${finalState.validationResult.reportPath}`);
233
+ }
234
234
  out("Generation pipeline completed. Validation passed.");
235
- await (0, hook_executor_1.runHookPhase)(cwd, "after_validate", out);
236
235
  });
237
236
  });
238
237
  program
@@ -393,34 +392,37 @@ async function runCli(options = {}) {
393
392
  });
394
393
  });
395
394
  program
396
- .command("skills", { hidden: true })
397
- .description("Advanced: manage and run skills")
395
+ .command("skills")
396
+ .description("Manage and run skills")
398
397
  .argument("[action]", "list or run", "list")
399
398
  .argument("[name]", "skill name (for run)")
400
399
  .option("--input <json>", "JSON input for skill execution")
401
400
  .action(async (action, name, opts) => {
402
- const { getGlobalSkillEngine } = await Promise.resolve().then(() => __importStar(require("../skills/engine")));
403
- const engine = getGlobalSkillEngine();
401
+ const { createEngine, createHydratedState } = await Promise.resolve().then(() => __importStar(require("../skill-engine")));
402
+ const engine = await createEngine(cwd, out);
403
+ const registry = engine.getRegistry();
404
404
  if (action === "list") {
405
- const manifests = engine.listSkills();
405
+ const manifests = registry.listManifests();
406
406
  if (manifests.length === 0) {
407
407
  out("No skills registered.");
408
408
  return;
409
409
  }
410
410
  out("Available skills:\n");
411
411
  for (const m of manifests) {
412
- out(` ${m.name.padEnd(25)} [${m.category}] ${m.description}`);
412
+ const deps = m.depends_on.length > 0 ? ` (deps: ${m.depends_on.join(", ")})` : "";
413
+ out(` ${m.name.padEnd(25)} [${m.category}] v${m.version} ${m.description}${deps}`);
413
414
  }
414
415
  return;
415
416
  }
416
417
  if (action === "run") {
417
418
  if (!name)
418
419
  throw new errors_1.UserError("Skill name is required. Usage: prodo skills run <name>");
420
+ const state = await createHydratedState(cwd);
419
421
  const inputs = opts.input ? JSON.parse(opts.input) : {};
420
422
  inputs.cwd = inputs.cwd ?? cwd;
421
- const result = await engine.execute(name, { cwd, log: out }, inputs);
423
+ const result = await engine.runSkill(name, state, { log: out });
422
424
  out(`\nSkill "${name}" completed.`);
423
- out(JSON.stringify(result, null, 2));
425
+ out(`Completed skills: ${result.completedSkills.join(" ")}`);
424
426
  return;
425
427
  }
426
428
  throw new errors_1.UserError(`Unknown skills action: "${action}". Use: list or run`);
@@ -2,7 +2,7 @@ import { type SupportedAi } from "./agent-command-installer";
2
2
  export type InitSelections = {
3
3
  ai?: SupportedAi;
4
4
  script: "sh" | "ps";
5
- lang: "tr" | "en";
5
+ lang: string;
6
6
  author: string;
7
7
  interactive: boolean;
8
8
  };
@@ -17,7 +17,7 @@ export declare function finishInitInteractive(summary: {
17
17
  projectRoot: string;
18
18
  settingsPath: string;
19
19
  ai?: SupportedAi;
20
- lang: "tr" | "en";
20
+ lang: string;
21
21
  author: string;
22
22
  }): Promise<void>;
23
23
  export {};
@@ -56,26 +56,20 @@ function renderProjectBox(projectName, projectRoot) {
56
56
  return [top, ...rows, bottom].join("\n");
57
57
  }
58
58
  async function detectAi(projectRoot) {
59
+ if (await (0, utils_1.fileExists)(node_path_1.default.join(projectRoot, ".claude")))
60
+ return "claude-cli";
59
61
  if (await (0, utils_1.fileExists)(node_path_1.default.join(projectRoot, ".agents")))
60
62
  return "codex";
61
63
  if (await (0, utils_1.fileExists)(node_path_1.default.join(projectRoot, ".gemini")))
62
64
  return "gemini-cli";
63
- if (await (0, utils_1.fileExists)(node_path_1.default.join(projectRoot, ".claude")))
64
- return "claude-cli";
65
65
  return undefined;
66
66
  }
67
67
  function normalizeLang(lang) {
68
- if ((lang ?? "").trim().toLowerCase().startsWith("tr"))
68
+ const raw = (lang ?? "").trim().toLowerCase();
69
+ if (raw.startsWith("tr"))
69
70
  return "tr";
70
71
  return "en";
71
72
  }
72
- function modelChoiceFromAi(ai) {
73
- if (ai === "codex")
74
- return "codex";
75
- if (ai === "gemini-cli")
76
- return "gemini";
77
- return "auto-detect";
78
- }
79
73
  function defaultAuthorName(authorInput) {
80
74
  const explicit = (authorInput ?? "").trim();
81
75
  if (explicit.length > 0)
@@ -86,7 +80,7 @@ function defaultAuthorName(authorInput) {
86
80
  return username;
87
81
  }
88
82
  catch {
89
- // ignore lookup errors and continue with fallback
83
+ // ignore
90
84
  }
91
85
  return "Product Author";
92
86
  }
@@ -106,11 +100,10 @@ async function gatherInitSelections(options) {
106
100
  };
107
101
  }
108
102
  const detectedAi = await detectAi(options.projectRoot);
109
- const initialModel = modelChoiceFromAi(parsedAi ?? detectedAi);
110
103
  const projectName = node_path_1.default.basename(options.projectRoot) || ".";
111
104
  const width = terminalWidth();
112
- const subtitle = color("Prodo — Product Artifact Toolkit", "\u001B[1;37m");
113
- const signature = color("Crafted by Codex, guided by Shahmarasy intelligence", "\u001B[38;5;244m");
105
+ const subtitle = color("Prodo — AI-Powered Product Owner", "\u001B[1;37m");
106
+ const signature = color("Built by Shahmarasy · Works with Claude, Codex, and Gemini", "\u001B[38;5;244m");
114
107
  const hero = [
115
108
  "",
116
109
  centerBlock(renderLogo().split("\n"), width),
@@ -121,25 +114,25 @@ async function gatherInitSelections(options) {
121
114
  ].join("\n");
122
115
  clack.intro(hero);
123
116
  clack.note(renderProjectBox(projectName, options.projectRoot), "Project Setup");
124
- const model = await clack.select({
125
- message: "Select model",
126
- initialValue: initialModel,
117
+ const agentChoice = await clack.select({
118
+ message: "Which AI agent will you use?",
119
+ initialValue: parsedAi ?? detectedAi ?? "claude-cli",
127
120
  options: [
128
- { value: "codex", label: "codex", hint: "Native Codex flow" },
129
- { value: "gemini", label: "gemini", hint: "Gemini CLI command set" },
130
- { value: "auto-detect", label: "auto-detect", hint: "Detect from local agent directories" }
121
+ { value: "claude-cli", label: "Claude Code", hint: "Slash commands → .claude/commands/" },
122
+ { value: "codex", label: "Codex", hint: "Skills .agents/skills/" },
123
+ { value: "gemini-cli", label: "Gemini CLI", hint: "Commands .gemini/commands/" }
131
124
  ]
132
125
  });
133
- if (clack.isCancel(model)) {
126
+ if (clack.isCancel(agentChoice)) {
134
127
  clack.cancel("Initialization cancelled.");
135
128
  throw new errors_1.UserError("Initialization cancelled.");
136
129
  }
137
130
  const lang = await clack.select({
138
- message: "Select language",
131
+ message: "Document language",
139
132
  initialValue: defaultLang,
140
133
  options: [
141
- { value: "tr", label: "tr", hint: "Turkish" },
142
- { value: "en", label: "en", hint: "English" }
134
+ { value: "en", label: "English" },
135
+ { value: "tr", label: "Türkçe" }
143
136
  ]
144
137
  });
145
138
  if (clack.isCancel(lang)) {
@@ -148,29 +141,43 @@ async function gatherInitSelections(options) {
148
141
  }
149
142
  const author = await clack.text({
150
143
  message: "Author name",
151
- placeholder: "Shahmarasy",
144
+ placeholder: "Your name",
152
145
  defaultValue: defaultAuthor
153
146
  });
154
147
  if (clack.isCancel(author)) {
155
148
  clack.cancel("Initialization cancelled.");
156
149
  throw new errors_1.UserError("Initialization cancelled.");
157
150
  }
158
- let selectedAi;
159
- if (model === "codex")
160
- selectedAi = "codex";
161
- else if (model === "gemini")
162
- selectedAi = "gemini-cli";
163
- else
164
- selectedAi = detectedAi;
165
151
  return {
166
- ai: selectedAi,
152
+ ai: agentChoice,
167
153
  script: fallbackScript,
168
- lang,
154
+ lang: String(lang),
169
155
  author: String(author).trim() || defaultAuthor,
170
156
  interactive: true
171
157
  };
172
158
  }
173
159
  function finishInitInteractive(summary) {
174
- const aiText = summary.ai ?? "none";
175
- return loadClack().then((clack) => clack.outro(`Scaffold complete.\nAI: ${aiText}\nLanguage: ${summary.lang}\nAuthor: ${summary.author}\nSettings: ${summary.settingsPath}\nNext: edit brief.md`));
160
+ const agentLabel = summary.ai === "claude-cli" ? "Claude Code"
161
+ : summary.ai === "codex" ? "Codex"
162
+ : summary.ai === "gemini-cli" ? "Gemini CLI"
163
+ : "none";
164
+ const commandPrefix = summary.ai === "codex" ? "$" : "/";
165
+ const commands = [
166
+ `${commandPrefix}prodo-normalize`,
167
+ `${commandPrefix}prodo-prd`,
168
+ `${commandPrefix}prodo-workflow`,
169
+ `${commandPrefix}prodo-wireframe`,
170
+ `${commandPrefix}prodo-stories`,
171
+ `${commandPrefix}prodo-techspec`,
172
+ `${commandPrefix}prodo-validate`
173
+ ];
174
+ return loadClack().then((clack) => clack.outro(`Scaffold complete!\n\n` +
175
+ ` Agent: ${agentLabel}\n` +
176
+ ` Language: ${summary.lang}\n` +
177
+ ` Author: ${summary.author}\n\n` +
178
+ `Next steps:\n` +
179
+ ` 1. Edit brief.md with your product description\n` +
180
+ ` 2. Open this folder in ${agentLabel}\n` +
181
+ ` 3. Run commands in sequence:\n` +
182
+ ` ${commands.join("\n ")}`));
176
183
  }
@@ -5,6 +5,7 @@ export declare function runInit(cwd: string, options?: {
5
5
  author?: string;
6
6
  preset?: string;
7
7
  script?: "sh" | "ps";
8
+ provider?: string;
8
9
  }): Promise<{
9
10
  installedAgentFiles: string[];
10
11
  settingsPath: string;
package/dist/cli/init.js CHANGED
@@ -328,7 +328,8 @@ async function runInit(cwd, options) {
328
328
  const settingsPath = await (0, settings_1.writeSettings)(cwd, {
329
329
  lang: (options?.lang ?? "en").trim() || "en",
330
330
  ai: options?.ai,
331
- author: (options?.author ?? "").trim() || undefined
331
+ author: (options?.author ?? "").trim() || undefined,
332
+ provider: options?.provider
332
333
  });
333
334
  return { installedAgentFiles, settingsPath };
334
335
  }
@@ -311,62 +311,62 @@ function renderWorkflowMarkdownForFeature(markdown, feature, lang) {
311
311
  }
312
312
  async function resolvePrompt(cwd, artifactType, templateContent, requiredHeadings, companionTemplate, outputAuthor, agent) {
313
313
  const base = await promises_1.default.readFile((0, paths_1.promptPath)(cwd, artifactType), "utf8");
314
- const authority = `Template authority (STRICT):
315
- - Treat this template as the single output structure source.
316
- - Keep heading order and names exactly as listed.
317
- - Do not invent new primary sections.
318
-
319
- Required headings (from template):
320
- ${requiredHeadings.map((heading) => `- ${heading}`).join("\n")}
321
-
322
- Resolved template:
323
- \`\`\`md
324
- ${templateContent.trim()}
314
+ const authority = `Template authority (STRICT):
315
+ - Treat this template as the single output structure source.
316
+ - Keep heading order and names exactly as listed.
317
+ - Do not invent new primary sections.
318
+
319
+ Required headings (from template):
320
+ ${requiredHeadings.map((heading) => `- ${heading}`).join("\n")}
321
+
322
+ Resolved template:
323
+ \`\`\`md
324
+ ${templateContent.trim()}
325
325
  \`\`\``;
326
326
  const companionAuthority = companionTemplate
327
- ? `Native companion template (STRICT reference):
328
- - Path: ${companionTemplate.path}
329
- - Preserve this native format and structure when generating companion artifact.
330
- \`\`\`${artifactType === "workflow" ? "mermaid" : "html"}
331
- ${companionTemplate.content.trim()}
327
+ ? `Native companion template (STRICT reference):
328
+ - Path: ${companionTemplate.path}
329
+ - Preserve this native format and structure when generating companion artifact.
330
+ \`\`\`${artifactType === "workflow" ? "mermaid" : "html"}
331
+ ${companionTemplate.content.trim()}
332
332
  \`\`\``
333
333
  : "";
334
334
  const workflowPairing = artifactType === "workflow"
335
- ? `
336
- Workflow paired output contract (STRICT):
337
- - Output markdown explanation first (template headings).
338
- - Then append a mermaid block for the same flow:
339
- \`\`\`mermaid
340
- flowchart TD
341
- ...
342
- \`\`\`
335
+ ? `
336
+ Workflow paired output contract (STRICT):
337
+ - Output markdown explanation first (template headings).
338
+ - Then append a mermaid block for the same flow:
339
+ \`\`\`mermaid
340
+ flowchart TD
341
+ ...
342
+ \`\`\`
343
343
  - Mermaid block is mandatory.`
344
344
  : "";
345
345
  const wireframePairing = artifactType === "wireframe"
346
- ? `
347
- Wireframe paired output contract (STRICT):
348
- - Output markdown explanation first (template headings).
349
- - Generate companion HTML screens based on native wireframe template.
346
+ ? `
347
+ Wireframe paired output contract (STRICT):
348
+ - Output markdown explanation first (template headings).
349
+ - Generate companion HTML screens based on native wireframe template.
350
350
  - HTML must stay low-fidelity and structure-first.`
351
351
  : "";
352
352
  const authorPolicy = outputAuthor && outputAuthor.trim().length > 0
353
- ? `
354
- Author policy (STRICT):
355
- - Use this exact author name wherever author is required: ${outputAuthor.trim()}
353
+ ? `
354
+ Author policy (STRICT):
355
+ - Use this exact author name wherever author is required: ${outputAuthor.trim()}
356
356
  - Do not invent random author names.`
357
357
  : "";
358
- const withTemplate = `${base}
359
-
360
- ${authority}
361
- ${companionAuthority}
362
- ${workflowPairing}
363
- ${wireframePairing}
358
+ const withTemplate = `${base}
359
+
360
+ ${authority}
361
+ ${companionAuthority}
362
+ ${workflowPairing}
363
+ ${wireframePairing}
364
364
  ${authorPolicy}`;
365
365
  if (!agent)
366
366
  return withTemplate;
367
- return `${withTemplate}
368
-
369
- Agent execution profile: ${agent}
367
+ return `${withTemplate}
368
+
369
+ Agent execution profile: ${agent}
370
370
  - Keep output deterministic and actionable.`;
371
371
  }
372
372
  async function loadLatestArtifactPath(cwd, type) {
@@ -574,38 +574,38 @@ async function writeWireframeScreens(targetDir, baseName, normalized, coverage,
574
574
  const screenBase = `${baseName}-${index + 1}-${toSlug(title)}`;
575
575
  const htmlPath = node_path_1.default.join(targetDir, `${screenBase}.html`);
576
576
  const mdPath = node_path_1.default.join(targetDir, `${screenBase}.md`);
577
- const fallbackHtml = `<!doctype html>
578
- <html lang="${lang}">
579
- <head>
580
- <meta charset="utf-8" />
581
- <meta name="viewport" content="width=device-width, initial-scale=1" />
582
- <title>${title}</title>
583
- </head>
584
- <body>
585
- <!-- [${screen.id}] -->
586
- <header>
587
- <h1>${title}</h1>
588
- <nav><button type="button">${(0, i18n_1.t)("back", lang)}</button><button type="button">${(0, i18n_1.t)("next", lang)}</button></nav>
589
- </header>
590
- <main>
591
- <section>
592
- <h2>${(0, i18n_1.t)("content", lang)}</h2>
593
- <ul>
594
- <li>${(0, i18n_1.t)("primary_info_area", lang)}</li>
595
- <li>${(0, i18n_1.t)("status_indicator", lang)}</li>
596
- </ul>
597
- </section>
598
- <section>
599
- <h2>${(0, i18n_1.t)("form", lang)}</h2>
600
- <form>
601
- <label>${(0, i18n_1.t)("field", lang)}
602
- <input type="text" name="field_${index + 1}" />
603
- </label>
604
- <button type="submit">${(0, i18n_1.t)("save", lang)}</button>
605
- </form>
606
- </section>
607
- </main>
608
- </body>
577
+ const fallbackHtml = `<!doctype html>
578
+ <html lang="${lang}">
579
+ <head>
580
+ <meta charset="utf-8" />
581
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
582
+ <title>${title}</title>
583
+ </head>
584
+ <body>
585
+ <!-- [${screen.id}] -->
586
+ <header>
587
+ <h1>${title}</h1>
588
+ <nav><button type="button">${(0, i18n_1.t)("back", lang)}</button><button type="button">${(0, i18n_1.t)("next", lang)}</button></nav>
589
+ </header>
590
+ <main>
591
+ <section>
592
+ <h2>${(0, i18n_1.t)("content", lang)}</h2>
593
+ <ul>
594
+ <li>${(0, i18n_1.t)("primary_info_area", lang)}</li>
595
+ <li>${(0, i18n_1.t)("status_indicator", lang)}</li>
596
+ </ul>
597
+ </section>
598
+ <section>
599
+ <h2>${(0, i18n_1.t)("form", lang)}</h2>
600
+ <form>
601
+ <label>${(0, i18n_1.t)("field", lang)}
602
+ <input type="text" name="field_${index + 1}" />
603
+ </label>
604
+ <button type="submit">${(0, i18n_1.t)("save", lang)}</button>
605
+ </form>
606
+ </section>
607
+ </main>
608
+ </body>
609
609
  </html>`;
610
610
  const htmlTemplate = htmlTemplateContent && htmlTemplateContent.trim().length > 0 ? htmlTemplateContent : fallbackHtml;
611
611
  const html = replaceTemplateTokens(htmlTemplate, {
@@ -2,6 +2,7 @@ export type ProdoSettings = {
2
2
  lang: string;
3
3
  ai?: string;
4
4
  author?: string;
5
+ provider?: string;
5
6
  };
6
7
  export declare function readSettings(cwd: string): Promise<ProdoSettings>;
7
8
  export declare function writeSettings(cwd: string, settings: ProdoSettings): Promise<string>;
@@ -21,7 +21,8 @@ async function readSettings(cwd) {
21
21
  return {
22
22
  lang: typeof parsed.lang === "string" && parsed.lang.trim() ? parsed.lang.trim() : "en",
23
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
24
+ author: typeof parsed.author === "string" && parsed.author.trim() ? parsed.author.trim() : undefined,
25
+ provider: typeof parsed.provider === "string" && parsed.provider.trim() ? parsed.provider.trim() : undefined
25
26
  };
26
27
  }
27
28
  catch {
@@ -30,6 +31,13 @@ async function readSettings(cwd) {
30
31
  }
31
32
  async function writeSettings(cwd, settings) {
32
33
  const path = (0, paths_1.settingsPath)(cwd);
33
- await promises_1.default.writeFile(path, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
34
+ const clean = { lang: settings.lang };
35
+ if (settings.ai)
36
+ clean.ai = settings.ai;
37
+ if (settings.author)
38
+ clean.author = settings.author;
39
+ if (settings.provider)
40
+ clean.provider = settings.provider;
41
+ await promises_1.default.writeFile(path, `${JSON.stringify(clean, null, 2)}\n`, "utf8");
34
42
  return path;
35
43
  }