@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.
- package/dist/agent-command-installer.js +6 -21
- package/dist/agents.js +15 -15
- package/dist/artifacts.js +105 -8
- package/dist/cli.js +6 -8
- package/dist/init.js +21 -0
- package/dist/template-resolver.d.ts +4 -0
- package/dist/template-resolver.js +18 -0
- package/dist/templates.js +5 -5
- package/package.json +1 -1
- package/src/agent-command-installer.ts +6 -21
- package/src/agents.ts +15 -15
- package/src/artifacts.ts +136 -8
- package/src/cli.ts +7 -9
- package/src/init.ts +17 -0
- package/src/template-resolver.ts +21 -0
- package/src/templates.ts +5 -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 +5 -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,19 @@ 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
|
${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 =
|
|
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
|
@@ -75,7 +75,44 @@ function extractTurkishTitle(featureText) {
|
|
|
75
75
|
return "Ekran";
|
|
76
76
|
return base;
|
|
77
77
|
}
|
|
78
|
-
|
|
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}
|
|
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
|
|
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("
|
|
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
|
|
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
|
|
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
|
@@ -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
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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 =
|
|
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}
|
|
39
|
-
{ command: `${prefix}
|
|
40
|
-
{ command: `${prefix}
|
|
41
|
-
{ command: `${prefix}
|
|
42
|
-
{ command: `${prefix}
|
|
43
|
-
{ command: `${prefix}
|
|
44
|
-
{ command: `${prefix}
|
|
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}
|
|
48
|
-
prd: `${prefix}
|
|
49
|
-
workflow: `${prefix}
|
|
50
|
-
wireframe: `${prefix}
|
|
51
|
-
stories: `${prefix}
|
|
52
|
-
techspec: `${prefix}
|
|
53
|
-
validate: `${prefix}
|
|
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
|
}
|