@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.
- package/dist/agent-command-installer.js +11 -21
- package/dist/agents.js +15 -15
- package/dist/artifacts.js +150 -13
- package/dist/cli.js +6 -9
- package/dist/init-tui.d.ts +0 -1
- package/dist/init-tui.js +2 -14
- package/dist/init.js +21 -0
- package/dist/providers/mock-provider.js +8 -1
- package/dist/template-resolver.d.ts +4 -0
- package/dist/template-resolver.js +18 -0
- package/dist/templates.js +6 -5
- package/package.json +1 -1
- package/src/agent-command-installer.ts +11 -21
- package/src/agents.ts +15 -15
- package/src/artifacts.ts +183 -12
- package/src/cli.ts +7 -10
- package/src/init-tui.ts +2 -16
- package/src/init.ts +17 -0
- package/src/providers/mock-provider.ts +12 -1
- package/src/template-resolver.ts +21 -0
- package/src/templates.ts +6 -5
- package/templates/artifacts/stories.md +11 -3
- package/templates/artifacts/wireframe.md +93 -15
- package/templates/artifacts/workflow.md +76 -16
- package/templates/commands/prodo-normalize.md +6 -6
- package/templates/commands/prodo-prd.md +6 -7
- package/templates/commands/prodo-stories.md +6 -7
- package/templates/commands/prodo-techspec.md +6 -7
- package/templates/commands/prodo-validate.md +6 -7
- package/templates/commands/prodo-wireframe.md +6 -7
- package/templates/commands/prodo-workflow.md +6 -7
|
@@ -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
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
+
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
|
-
|
|
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 =
|
|
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}
|
|
30
|
-
{ command: `${prefix}
|
|
31
|
-
{ command: `${prefix}
|
|
32
|
-
{ command: `${prefix}
|
|
33
|
-
{ command: `${prefix}
|
|
34
|
-
{ command: `${prefix}
|
|
35
|
-
{ command: `${prefix}
|
|
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}
|
|
39
|
-
prd: `${prefix}
|
|
40
|
-
workflow: `${prefix}
|
|
41
|
-
wireframe: `${prefix}
|
|
42
|
-
stories: `${prefix}
|
|
43
|
-
techspec: `${prefix}
|
|
44
|
-
validate: `${prefix}
|
|
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 =
|
|
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 (
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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}
|
|
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
|
|
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("
|
|
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
|
|
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")
|
package/dist/init-tui.d.ts
CHANGED
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}\
|
|
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,
|
|
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
|
|
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.
|