@shahmarasy/prodo 0.1.4 → 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 (205) hide show
  1. package/README.md +201 -97
  2. package/bin/prodo.cjs +6 -6
  3. package/dist/agents/agent-registry.d.ts +13 -0
  4. package/dist/agents/agent-registry.js +79 -0
  5. package/dist/agents/anthropic/index.d.ts +9 -0
  6. package/dist/agents/anthropic/index.js +55 -0
  7. package/dist/agents/base.d.ts +25 -0
  8. package/dist/agents/base.js +71 -0
  9. package/dist/agents/google/index.d.ts +9 -0
  10. package/dist/agents/google/index.js +53 -0
  11. package/dist/agents/mock/index.d.ts +11 -0
  12. package/dist/agents/mock/index.js +26 -0
  13. package/dist/agents/openai/index.d.ts +9 -0
  14. package/dist/agents/openai/index.js +57 -0
  15. package/dist/agents/system-prompts.d.ts +3 -0
  16. package/dist/agents/system-prompts.js +32 -0
  17. package/dist/cli/agent-command-installer.d.ts +4 -0
  18. package/dist/cli/agent-command-installer.js +148 -0
  19. package/dist/cli/agent-ids.d.ts +15 -0
  20. package/dist/cli/agent-ids.js +49 -0
  21. package/dist/cli/doctor.d.ts +1 -0
  22. package/dist/cli/doctor.js +144 -0
  23. package/dist/cli/fix-tui.d.ts +4 -0
  24. package/dist/cli/fix-tui.js +79 -0
  25. package/dist/cli/index.d.ts +9 -0
  26. package/dist/cli/index.js +467 -0
  27. package/dist/cli/init-tui.d.ts +23 -0
  28. package/dist/cli/init-tui.js +183 -0
  29. package/dist/cli/init.d.ts +12 -0
  30. package/dist/cli/init.js +335 -0
  31. package/dist/cli/normalize-interactive.d.ts +8 -0
  32. package/dist/cli/normalize-interactive.js +167 -0
  33. package/dist/cli/preset-loader.d.ts +4 -0
  34. package/dist/cli/preset-loader.js +210 -0
  35. package/dist/core/artifact-registry.d.ts +11 -0
  36. package/dist/core/artifact-registry.js +49 -0
  37. package/dist/core/artifacts.d.ts +10 -0
  38. package/dist/core/artifacts.js +892 -0
  39. package/dist/core/clean.d.ts +10 -0
  40. package/dist/core/clean.js +74 -0
  41. package/dist/core/consistency.d.ts +8 -0
  42. package/dist/core/consistency.js +328 -0
  43. package/dist/core/constants.d.ts +7 -0
  44. package/dist/core/constants.js +64 -0
  45. package/dist/core/errors.d.ts +3 -0
  46. package/dist/core/errors.js +10 -0
  47. package/dist/core/fix.d.ts +31 -0
  48. package/dist/core/fix.js +188 -0
  49. package/dist/core/hook-executor.d.ts +1 -0
  50. package/dist/core/hook-executor.js +175 -0
  51. package/dist/core/markdown.d.ts +16 -0
  52. package/dist/core/markdown.js +81 -0
  53. package/dist/core/normalize.d.ts +8 -0
  54. package/dist/core/normalize.js +125 -0
  55. package/dist/core/normalized-brief.d.ts +48 -0
  56. package/dist/core/normalized-brief.js +182 -0
  57. package/dist/core/output-index.d.ts +13 -0
  58. package/dist/core/output-index.js +55 -0
  59. package/dist/core/paths.d.ts +17 -0
  60. package/dist/core/paths.js +80 -0
  61. package/dist/core/project-config.d.ts +14 -0
  62. package/dist/core/project-config.js +69 -0
  63. package/dist/core/registry.d.ts +13 -0
  64. package/dist/core/registry.js +115 -0
  65. package/dist/core/settings.d.ts +8 -0
  66. package/dist/core/settings.js +43 -0
  67. package/dist/core/template-engine.d.ts +3 -0
  68. package/dist/core/template-engine.js +43 -0
  69. package/dist/core/template-resolver.d.ts +15 -0
  70. package/dist/core/template-resolver.js +46 -0
  71. package/dist/core/templates.d.ts +33 -0
  72. package/dist/core/templates.js +440 -0
  73. package/dist/core/terminology.d.ts +21 -0
  74. package/dist/core/terminology.js +143 -0
  75. package/dist/core/tracing.d.ts +21 -0
  76. package/dist/core/tracing.js +74 -0
  77. package/dist/core/types.d.ts +35 -0
  78. package/dist/core/types.js +5 -0
  79. package/dist/core/utils.d.ts +7 -0
  80. package/dist/core/utils.js +66 -0
  81. package/dist/core/validate.d.ts +10 -0
  82. package/dist/core/validate.js +226 -0
  83. package/dist/core/validator.d.ts +5 -0
  84. package/dist/core/validator.js +76 -0
  85. package/dist/core/version.d.ts +1 -0
  86. package/dist/core/version.js +30 -0
  87. package/dist/core/workflow-commands.d.ts +7 -0
  88. package/dist/core/workflow-commands.js +29 -0
  89. package/dist/i18n/en.json +45 -0
  90. package/dist/i18n/index.d.ts +5 -0
  91. package/dist/i18n/index.js +63 -0
  92. package/dist/i18n/tr.json +45 -0
  93. package/dist/providers/index.d.ts +2 -1
  94. package/dist/providers/index.js +20 -6
  95. package/dist/providers/mock-provider.d.ts +1 -1
  96. package/dist/providers/mock-provider.js +12 -11
  97. package/dist/providers/openai-provider.d.ts +1 -1
  98. package/dist/providers/openai-provider.js +13 -13
  99. package/dist/skill-engine/context.d.ts +7 -0
  100. package/dist/skill-engine/context.js +76 -0
  101. package/dist/skill-engine/discovery.d.ts +2 -0
  102. package/dist/skill-engine/discovery.js +52 -0
  103. package/dist/skill-engine/graph.d.ts +4 -0
  104. package/dist/skill-engine/graph.js +114 -0
  105. package/dist/skill-engine/index.d.ts +11 -0
  106. package/dist/skill-engine/index.js +49 -0
  107. package/dist/skill-engine/pipeline.d.ts +9 -0
  108. package/dist/skill-engine/pipeline.js +84 -0
  109. package/dist/skill-engine/registry.d.ts +12 -0
  110. package/dist/skill-engine/registry.js +74 -0
  111. package/dist/skill-engine/types.d.ts +66 -0
  112. package/dist/skill-engine/types.js +2 -0
  113. package/dist/skill-engine/validator.d.ts +4 -0
  114. package/dist/skill-engine/validator.js +90 -0
  115. package/dist/skills/engine.d.ts +10 -0
  116. package/dist/skills/engine.js +75 -0
  117. package/dist/skills/fix-skill.d.ts +2 -0
  118. package/dist/skills/fix-skill.js +38 -0
  119. package/dist/skills/fix.d.ts +2 -0
  120. package/dist/skills/fix.js +41 -0
  121. package/dist/skills/generate-artifact-skill.d.ts +2 -0
  122. package/dist/skills/generate-artifact-skill.js +32 -0
  123. package/dist/skills/generate-artifact.d.ts +2 -0
  124. package/dist/skills/generate-artifact.js +42 -0
  125. package/dist/skills/generate-pipeline-skill.d.ts +2 -0
  126. package/dist/skills/generate-pipeline-skill.js +45 -0
  127. package/dist/skills/normalize-skill.d.ts +2 -0
  128. package/dist/skills/normalize-skill.js +29 -0
  129. package/dist/skills/normalize.d.ts +2 -0
  130. package/dist/skills/normalize.js +29 -0
  131. package/dist/skills/register-core.d.ts +2 -0
  132. package/dist/skills/register-core.js +21 -0
  133. package/dist/skills/types.d.ts +28 -0
  134. package/dist/skills/types.js +2 -0
  135. package/dist/skills/validate-skill.d.ts +2 -0
  136. package/dist/skills/validate-skill.js +29 -0
  137. package/dist/skills/validate.d.ts +2 -0
  138. package/dist/skills/validate.js +37 -0
  139. package/package.json +72 -45
  140. package/src/agents/agent-registry.ts +93 -0
  141. package/src/agents/anthropic/index.ts +86 -0
  142. package/src/agents/anthropic/manifest.json +7 -0
  143. package/src/agents/base.ts +77 -0
  144. package/src/agents/google/index.ts +79 -0
  145. package/src/agents/google/manifest.json +7 -0
  146. package/src/agents/mock/index.ts +32 -0
  147. package/src/agents/mock/manifest.json +7 -0
  148. package/src/agents/openai/index.ts +83 -0
  149. package/src/agents/openai/manifest.json +7 -0
  150. package/src/agents/system-prompts.ts +35 -0
  151. package/src/{agent-command-installer.ts → cli/agent-command-installer.ts} +164 -164
  152. package/src/{agents.ts → cli/agent-ids.ts} +58 -58
  153. package/src/{doctor.ts → cli/doctor.ts} +157 -137
  154. package/src/cli/fix-tui.ts +111 -0
  155. package/src/{cli.ts → cli/index.ts} +463 -410
  156. package/src/{init-tui.ts → cli/init-tui.ts} +49 -37
  157. package/src/{init.ts → cli/init.ts} +399 -398
  158. package/src/cli/normalize-interactive.ts +241 -0
  159. package/src/{preset-loader.ts → cli/preset-loader.ts} +237 -237
  160. package/src/{artifact-registry.ts → core/artifact-registry.ts} +69 -69
  161. package/src/{artifacts.ts → core/artifacts.ts} +1081 -1072
  162. package/src/core/clean.ts +88 -0
  163. package/src/{consistency.ts → core/consistency.ts} +374 -303
  164. package/src/{constants.ts → core/constants.ts} +72 -72
  165. package/src/{errors.ts → core/errors.ts} +7 -7
  166. package/src/core/fix.ts +253 -0
  167. package/src/{hook-executor.ts → core/hook-executor.ts} +196 -196
  168. package/src/{markdown.ts → core/markdown.ts} +93 -73
  169. package/src/{normalize.ts → core/normalize.ts} +145 -137
  170. package/src/{normalized-brief.ts → core/normalized-brief.ts} +227 -206
  171. package/src/{output-index.ts → core/output-index.ts} +59 -59
  172. package/src/{paths.ts → core/paths.ts} +75 -71
  173. package/src/{project-config.ts → core/project-config.ts} +78 -78
  174. package/src/{registry.ts → core/registry.ts} +119 -119
  175. package/src/{settings.ts → core/settings.ts} +8 -2
  176. package/src/core/template-engine.ts +45 -0
  177. package/src/{template-resolver.ts → core/template-resolver.ts} +54 -54
  178. package/src/{templates.ts → core/templates.ts} +452 -452
  179. package/src/core/terminology.ts +177 -0
  180. package/src/core/tracing.ts +110 -0
  181. package/src/{types.ts → core/types.ts} +46 -46
  182. package/src/{utils.ts → core/utils.ts} +64 -64
  183. package/src/{validate.ts → core/validate.ts} +252 -246
  184. package/src/{validator.ts → core/validator.ts} +92 -92
  185. package/src/{version.ts → core/version.ts} +24 -24
  186. package/src/{workflow-commands.ts → core/workflow-commands.ts} +32 -32
  187. package/src/i18n/en.json +45 -0
  188. package/src/i18n/index.ts +58 -0
  189. package/src/i18n/tr.json +45 -0
  190. package/src/providers/index.ts +29 -12
  191. package/src/providers/mock-provider.ts +200 -199
  192. package/src/providers/openai-provider.ts +88 -88
  193. package/src/skill-engine/context.ts +90 -0
  194. package/src/skill-engine/discovery.ts +57 -0
  195. package/src/skill-engine/graph.ts +136 -0
  196. package/src/skill-engine/index.ts +55 -0
  197. package/src/skill-engine/pipeline.ts +112 -0
  198. package/src/skill-engine/registry.ts +75 -0
  199. package/src/skill-engine/types.ts +81 -0
  200. package/src/skill-engine/validator.ts +135 -0
  201. package/src/skills/fix.ts +45 -0
  202. package/src/skills/generate-artifact.ts +48 -0
  203. package/src/skills/normalize.ts +32 -0
  204. package/src/skills/register-core.ts +27 -0
  205. package/src/skills/validate.ts +40 -0
@@ -0,0 +1,892 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateArtifact = generateArtifact;
7
+ const promises_1 = __importDefault(require("node:fs/promises"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const gray_matter_1 = __importDefault(require("gray-matter"));
10
+ const i18n_1 = require("../i18n");
11
+ const constants_1 = require("./constants");
12
+ const artifact_registry_1 = require("./artifact-registry");
13
+ const errors_1 = require("./errors");
14
+ const normalized_brief_1 = require("./normalized-brief");
15
+ const output_index_1 = require("./output-index");
16
+ const paths_1 = require("./paths");
17
+ const providers_1 = require("../providers");
18
+ const template_resolver_1 = require("./template-resolver");
19
+ const settings_1 = require("./settings");
20
+ const markdown_1 = require("./markdown");
21
+ const utils_1 = require("./utils");
22
+ const validator_1 = require("./validator");
23
+ function defaultFilename(type) {
24
+ return `${type}-${(0, utils_1.artifactFileStamp)()}.md`;
25
+ }
26
+ function sidecarPath(filePath) {
27
+ const parsed = node_path_1.default.parse(filePath);
28
+ return node_path_1.default.join(parsed.dir, `${parsed.name}.artifact.json`);
29
+ }
30
+ async function writeSidecar(filePath, doc) {
31
+ const payload = {
32
+ frontmatter: doc.frontmatter,
33
+ body: doc.body
34
+ };
35
+ await promises_1.default.writeFile(sidecarPath(filePath), `${JSON.stringify(payload, null, 2)}\n`, "utf8");
36
+ }
37
+ async function loadArtifactDoc(filePath) {
38
+ const sidecar = sidecarPath(filePath);
39
+ if (await (0, utils_1.fileExists)(sidecar)) {
40
+ const loaded = await (0, utils_1.readJsonFile)(sidecar);
41
+ return {
42
+ frontmatter: loaded.frontmatter ?? {},
43
+ body: typeof loaded.body === "string" ? loaded.body : ""
44
+ };
45
+ }
46
+ const raw = await promises_1.default.readFile(filePath, "utf8");
47
+ const parsed = (0, gray_matter_1.default)(raw);
48
+ return {
49
+ frontmatter: parsed.data,
50
+ body: parsed.content
51
+ };
52
+ }
53
+ function languageProbe(body) {
54
+ const stripped = body
55
+ .replace(/```[\s\S]*?```/g, " ")
56
+ .replace(/^\s*#{1,6}\s+.*$/gm, " ")
57
+ .replace(/<[^>]+>/g, " ")
58
+ .replace(/\|/g, " ")
59
+ .replace(/\s+/g, " ")
60
+ .trim()
61
+ .toLowerCase();
62
+ return ` ${stripped} `;
63
+ }
64
+ function hasEnglishLeak(body) {
65
+ const englishMarkers = [" the ", " and ", " with ", " user ", " should ", " must ", " requirement ", " flow ", " error ", " success "];
66
+ const normalized = languageProbe(body);
67
+ return englishMarkers.filter((m) => normalized.includes(m)).length >= 2;
68
+ }
69
+ function hasTurkishLeak(body) {
70
+ const turkishMarkers = [
71
+ " ve ",
72
+ " ile ",
73
+ " kullanici ",
74
+ " kullanıcı ",
75
+ " akis ",
76
+ " akış ",
77
+ " hata ",
78
+ " basari ",
79
+ " başarı ",
80
+ " ekran ",
81
+ " islem ",
82
+ " işlem ",
83
+ " gerekli "
84
+ ];
85
+ const normalized = languageProbe(body);
86
+ return turkishMarkers.filter((m) => normalized.includes(m)).length >= 2;
87
+ }
88
+ function enforceLanguage(body, lang, artifactType) {
89
+ const normalized = (lang || "en").toLowerCase();
90
+ if (normalized.startsWith("tr")) {
91
+ if (!hasEnglishLeak(body))
92
+ return;
93
+ throw new errors_1.UserError(`Language enforcement failed for ${artifactType}: output contains English fragments while language is Turkish.`);
94
+ }
95
+ if (normalized.startsWith("en")) {
96
+ if (!hasTurkishLeak(body))
97
+ return;
98
+ throw new errors_1.UserError(`Language enforcement failed for ${artifactType}: output contains Turkish fragments while language is English.`);
99
+ }
100
+ }
101
+ function toSlug(value) {
102
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "screen";
103
+ }
104
+ function extractTurkishTitle(featureText) {
105
+ const base = featureText.replace(/^\[[A-Z][0-9]+\]\s*/, "").trim();
106
+ if (!base)
107
+ return "Ekran";
108
+ return base;
109
+ }
110
+ function replaceTemplateTokens(template, replacements, fallbackFromToken) {
111
+ const context = {};
112
+ for (const [key, value] of Object.entries(replacements)) {
113
+ const nunjucksKey = key.replace(/[^a-zA-Z0-9_]/g, "_");
114
+ context[nunjucksKey] = value;
115
+ }
116
+ let prepared = template;
117
+ for (const [key, value] of Object.entries(replacements)) {
118
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
119
+ prepared = prepared.replace(new RegExp(`\\{\\{\\s*${escaped}\\s*\\}\\}`, "g"), value);
120
+ }
121
+ return prepared.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_match, tokenRaw) => {
122
+ const token = String(tokenRaw).trim();
123
+ if (token.includes("|") || token.includes("%"))
124
+ return _match;
125
+ return fallbackFromToken(token);
126
+ });
127
+ }
128
+ function renderWorkflowMermaidTemplate(templateContent, normalized, coverage, lang) {
129
+ const primaryFeatureId = coverage.core_features[0] ?? normalized.contracts.core_features[0]?.id ?? "F1";
130
+ const primaryFeatureText = normalized.contracts.core_features.find((item) => item.id === primaryFeatureId)?.text ??
131
+ normalized.contracts.core_features[0]?.text ??
132
+ (0, i18n_1.t)("user_action", lang);
133
+ return replaceTemplateTokens(templateContent, {
134
+ "Flow Name": (0, i18n_1.t)("main_flow", lang),
135
+ "Primary Actor": normalized.audience[0] ?? (0, i18n_1.t)("user", lang),
136
+ "Primary Action": `[${primaryFeatureId}] ${primaryFeatureText}`,
137
+ "Success State": (0, i18n_1.t)("success", lang),
138
+ "Error State": (0, i18n_1.t)("error", lang)
139
+ }, (token) => {
140
+ const key = token.toLowerCase();
141
+ if (key.includes("actor") || key.includes("user"))
142
+ return normalized.audience[0] ?? (0, i18n_1.t)("user", lang);
143
+ if (key.includes("action") || key.includes("feature"))
144
+ return `[${primaryFeatureId}] ${primaryFeatureText}`;
145
+ if (key.includes("success"))
146
+ return (0, i18n_1.t)("success", lang);
147
+ if (key.includes("error") || key.includes("fail"))
148
+ return (0, i18n_1.t)("error", lang);
149
+ if (key.includes("flow"))
150
+ return (0, i18n_1.t)("main_flow", lang);
151
+ return token;
152
+ });
153
+ }
154
+ function normalizeAuthor(author) {
155
+ if (!author)
156
+ return undefined;
157
+ const normalized = author.trim();
158
+ return normalized.length > 0 ? normalized : undefined;
159
+ }
160
+ function replaceAuthorPlaceholders(body, author) {
161
+ const safeAuthor = normalizeAuthor(author);
162
+ if (!safeAuthor)
163
+ return body;
164
+ return body.replace(/\{\{\s*author\s*\}\}/gi, safeAuthor);
165
+ }
166
+ function todayYmd() {
167
+ const now = new Date();
168
+ const y = now.getFullYear();
169
+ const m = String(now.getMonth() + 1).padStart(2, "0");
170
+ const d = String(now.getDate()).padStart(2, "0");
171
+ return `${y}-${m}-${d}`;
172
+ }
173
+ function headingKey(value) {
174
+ return value
175
+ .toLowerCase()
176
+ .replace(/[^a-z0-9]+/g, " ")
177
+ .trim();
178
+ }
179
+ function defaultDocumentControlValues(lang, revisionType, version, author) {
180
+ const safeAuthor = normalizeAuthor(author) ?? "Prodo";
181
+ const description = revisionType === "fix"
182
+ ? (0, i18n_1.t)("fix_revision", lang)
183
+ : (0, i18n_1.t)("initial_version", lang);
184
+ return {
185
+ version,
186
+ date: todayYmd(),
187
+ author: safeAuthor,
188
+ description
189
+ };
190
+ }
191
+ function applyDocumentControlDefaults(body, options) {
192
+ const defaults = defaultDocumentControlValues(options.lang, options.revisionType, options.version, options.author);
193
+ let out = body
194
+ .replace(/\{\{\s*date\s*\}\}/gi, defaults.date)
195
+ .replace(/\{\{\s*description\s*\}\}/gi, defaults.description)
196
+ .replace(/\{\{\s*version\s*\}\}/gi, defaults.version);
197
+ const lines = out.split(/\r?\n/);
198
+ const headingIndex = lines.findIndex((line) => {
199
+ const match = line.match(/^\s*##+\s+(.+?)\s*$/);
200
+ if (!match)
201
+ return false;
202
+ const key = headingKey(match[1]);
203
+ return key.includes("document control") || key.includes("belge kontrol");
204
+ });
205
+ if (headingIndex === -1)
206
+ return out;
207
+ const row = `| ${defaults.version} | ${defaults.date} | ${defaults.author} | ${defaults.description} |`;
208
+ let tableSeparatorIndex = -1;
209
+ let tableDataIndex = -1;
210
+ for (let i = headingIndex + 1; i < lines.length; i += 1) {
211
+ if (/^\s*##+\s+/.test(lines[i]))
212
+ break;
213
+ if (tableSeparatorIndex === -1 && /\|/.test(lines[i]) && /-/.test(lines[i])) {
214
+ tableSeparatorIndex = i;
215
+ continue;
216
+ }
217
+ if (tableSeparatorIndex !== -1 && /^\s*\|/.test(lines[i])) {
218
+ tableDataIndex = i;
219
+ break;
220
+ }
221
+ }
222
+ if (tableDataIndex !== -1) {
223
+ lines[tableDataIndex] = row;
224
+ }
225
+ else if (tableSeparatorIndex !== -1) {
226
+ lines.splice(tableSeparatorIndex + 1, 0, row);
227
+ }
228
+ else {
229
+ lines.splice(headingIndex + 1, 0, "", "| Version | Date | Author | Description |", "|--------|------|--------|-------------|", row, "");
230
+ }
231
+ out = lines.join("\n");
232
+ return out;
233
+ }
234
+ function parseVersionToken(input) {
235
+ const match = input.match(/v?\s*(\d+)(?:\.(\d+))?/i);
236
+ if (!match)
237
+ return null;
238
+ const major = Number(match[1]);
239
+ const minor = Number(match[2] ?? "0");
240
+ if (!Number.isFinite(major) || !Number.isFinite(minor))
241
+ return null;
242
+ return { major, minor };
243
+ }
244
+ function extractDocumentControlVersion(body) {
245
+ const tableMatch = body.match(/\|\s*(v?\d+(?:\.\d+)?)\s*\|/i);
246
+ if (tableMatch?.[1])
247
+ return tableMatch[1].trim().startsWith("v") ? tableMatch[1].trim() : `v${tableMatch[1].trim()}`;
248
+ const looseMatch = body.match(/\bv?\d+\.\d+\b/i);
249
+ if (looseMatch?.[0])
250
+ return looseMatch[0].startsWith("v") ? looseMatch[0] : `v${looseMatch[0]}`;
251
+ return undefined;
252
+ }
253
+ async function resolveDocumentControlVersion(cwd, artifactType, revisionType) {
254
+ if (revisionType !== "fix")
255
+ return "v1.0";
256
+ const activePath = await (0, output_index_1.getActiveArtifactPath)(cwd, artifactType);
257
+ const fallbackPath = activePath ?? (await loadLatestArtifactPath(cwd, artifactType));
258
+ if (!fallbackPath || !(await (0, utils_1.fileExists)(fallbackPath))) {
259
+ return "v1.1";
260
+ }
261
+ try {
262
+ const previous = await loadArtifactDoc(fallbackPath);
263
+ const previousVersion = extractDocumentControlVersion(previous.body) ?? String(previous.frontmatter.version ?? "");
264
+ const parsed = parseVersionToken(previousVersion);
265
+ if (!parsed)
266
+ return "v1.1";
267
+ return `v${parsed.major}.${parsed.minor + 1}`;
268
+ }
269
+ catch {
270
+ return "v1.1";
271
+ }
272
+ }
273
+ function enforceAuthorInControlTables(body, author) {
274
+ const safeAuthor = normalizeAuthor(author);
275
+ if (!safeAuthor)
276
+ return body;
277
+ return body.replace(/(\|\s*v?[0-9.]+\s*\|\s*[^|]*\|\s*)([^|]*)(\|\s*[^|]*\|)/gi, (_match, left, _current, right) => `${left}${safeAuthor} ${right}`);
278
+ }
279
+ async function resolveUniqueOutputPath(targetDir, fileName) {
280
+ const parsed = node_path_1.default.parse(fileName);
281
+ let candidate = node_path_1.default.join(targetDir, fileName);
282
+ let index = 2;
283
+ while (await (0, utils_1.fileExists)(candidate)) {
284
+ candidate = node_path_1.default.join(targetDir, `${parsed.name}-${String(index).padStart(2, "0")}${parsed.ext}`);
285
+ index += 1;
286
+ }
287
+ return candidate;
288
+ }
289
+ function workflowFeatureTargets(normalized, coverage) {
290
+ const byId = new Map(normalized.contracts.core_features.map((item) => [item.id, item]));
291
+ const explicit = coverage.core_features
292
+ .map((id) => byId.get(id))
293
+ .filter((item) => Boolean(item));
294
+ if (explicit.length > 1)
295
+ return explicit;
296
+ if (normalized.contracts.core_features.length > 1)
297
+ return normalized.contracts.core_features.slice(0, 6);
298
+ if (explicit.length === 1)
299
+ return explicit;
300
+ return normalized.contracts.core_features.slice(0, 1);
301
+ }
302
+ function renderWorkflowMarkdownForFeature(markdown, feature, lang) {
303
+ const tr = lang.toLowerCase().startsWith("tr");
304
+ const noteHeading = "## " + (0, i18n_1.t)("flow_focus", lang);
305
+ const noteLine = tr
306
+ ? `- [${feature.id}] Bu akis ${feature.text} ihtiyacina odaklanir.`
307
+ : `- [${feature.id}] This flow focuses on ${feature.text}.`;
308
+ if (markdown.includes(noteHeading))
309
+ return markdown;
310
+ return `${markdown.trim()}\n\n${noteHeading}\n${noteLine}`.trim();
311
+ }
312
+ async function resolvePrompt(cwd, artifactType, templateContent, requiredHeadings, companionTemplate, outputAuthor, agent) {
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()}
325
+ \`\`\``;
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()}
332
+ \`\`\``
333
+ : "";
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
+ \`\`\`
343
+ - Mermaid block is mandatory.`
344
+ : "";
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.
350
+ - HTML must stay low-fidelity and structure-first.`
351
+ : "";
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()}
356
+ - Do not invent random author names.`
357
+ : "";
358
+ const withTemplate = `${base}
359
+
360
+ ${authority}
361
+ ${companionAuthority}
362
+ ${workflowPairing}
363
+ ${wireframePairing}
364
+ ${authorPolicy}`;
365
+ if (!agent)
366
+ return withTemplate;
367
+ return `${withTemplate}
368
+
369
+ Agent execution profile: ${agent}
370
+ - Keep output deterministic and actionable.`;
371
+ }
372
+ async function loadLatestArtifactPath(cwd, type) {
373
+ const def = await (0, artifact_registry_1.getArtifactDefinition)(cwd, type);
374
+ const active = await (0, output_index_1.getActiveArtifactPath)(cwd, type);
375
+ if (active)
376
+ return active;
377
+ const files = await (0, utils_1.listFilesSortedByMtime)((0, paths_1.outputDirPath)(cwd, type, def.output_dir));
378
+ return files[0];
379
+ }
380
+ function contextFilePath(cwd, artifactFile) {
381
+ const base = node_path_1.default.parse(artifactFile).name;
382
+ return node_path_1.default.join((0, paths_1.outputContextDirPath)(cwd), `${base}.json`);
383
+ }
384
+ function toLineItems(value) {
385
+ if (!value)
386
+ return [];
387
+ return value
388
+ .split(/\r?\n/)
389
+ .map((line) => line.replace(/^\s*[-*0-9.]+\s*/, "").trim())
390
+ .filter((line) => line.length > 0);
391
+ }
392
+ function parseHeadingTitle(fullHeading) {
393
+ return fullHeading.replace(/^##\s+/, "").trim();
394
+ }
395
+ function deriveStructuredContext(artifactType, body, requiredHeadings) {
396
+ const sections = (0, markdown_1.sectionTextMap)(body);
397
+ const ordered = requiredHeadings
398
+ .map((heading) => ({ heading, items: toLineItems(sections.get(heading)) }))
399
+ .filter((item) => item.items.length > 0);
400
+ const section_map = Object.fromEntries(Array.from(sections.entries()).map(([heading, text]) => [parseHeadingTitle(heading), toLineItems(text)]));
401
+ if (artifactType === "workflow") {
402
+ return {
403
+ section_map,
404
+ actor_map: ordered[0]?.items ?? [],
405
+ step_map: ordered[1]?.items ?? [],
406
+ edge_case_map: ordered[2]?.items ?? []
407
+ };
408
+ }
409
+ if (artifactType === "wireframe") {
410
+ return {
411
+ section_map,
412
+ screen_map: ordered[0]?.items ?? [],
413
+ interaction_map: ordered[1]?.items ?? []
414
+ };
415
+ }
416
+ if (artifactType === "techspec") {
417
+ return {
418
+ section_map,
419
+ architecture_map: ordered[0]?.items ?? [],
420
+ integration_map: ordered[1]?.items ?? []
421
+ };
422
+ }
423
+ if (artifactType === "stories") {
424
+ return {
425
+ section_map,
426
+ story_map: ordered[0]?.items ?? [],
427
+ acceptance_map: ordered[1]?.items ?? []
428
+ };
429
+ }
430
+ return {
431
+ section_map,
432
+ goal_map: ordered[0]?.items ?? [],
433
+ requirement_map: ordered[1]?.items ?? []
434
+ };
435
+ }
436
+ async function buildUpstreamArtifacts(cwd, artifactType, upstreamTypes) {
437
+ const refs = [];
438
+ for (const type of upstreamTypes) {
439
+ const latest = await loadLatestArtifactPath(cwd, type);
440
+ if (!latest)
441
+ continue;
442
+ const parsed = await loadArtifactDoc(latest);
443
+ const frontmatter = parsed.frontmatter;
444
+ const coverageRaw = frontmatter.contract_coverage;
445
+ const contextPath = contextFilePath(cwd, latest);
446
+ const structuredContext = (await (0, utils_1.fileExists)(contextPath))
447
+ ? await (0, utils_1.readJsonFile)(contextPath)
448
+ : {};
449
+ refs.push({
450
+ type,
451
+ file: latest,
452
+ contractCoverage: {
453
+ goals: Array.isArray(coverageRaw?.goals)
454
+ ? coverageRaw.goals.filter((item) => typeof item === "string")
455
+ : [],
456
+ core_features: Array.isArray(coverageRaw?.core_features)
457
+ ? coverageRaw.core_features.filter((item) => typeof item === "string")
458
+ : [],
459
+ constraints: Array.isArray(coverageRaw?.constraints)
460
+ ? coverageRaw.constraints.filter((item) => typeof item === "string")
461
+ : []
462
+ },
463
+ ...(Object.keys(structuredContext).length > 0 ? { structuredContext } : {})
464
+ });
465
+ }
466
+ return refs;
467
+ }
468
+ function extractCoverageFromBody(body) {
469
+ const tagged = {
470
+ goals: Array.from(new Set(body.match(/\[(G[0-9]+)\]/g)?.map((item) => item.slice(1, -1)) ?? [])),
471
+ core_features: Array.from(new Set(body.match(/\[(F[0-9]+)\]/g)?.map((item) => item.slice(1, -1)) ?? [])),
472
+ constraints: Array.from(new Set(body.match(/\[(C[0-9]+)\]/g)?.map((item) => item.slice(1, -1)) ?? []))
473
+ };
474
+ return tagged;
475
+ }
476
+ function missingCoverage(requiredContracts, normalized, coverage) {
477
+ const ids = (0, normalized_brief_1.contractIds)(normalized.contracts);
478
+ const missing = [];
479
+ for (const key of requiredContracts) {
480
+ const expected = ids[key];
481
+ if (expected.length === 0)
482
+ continue;
483
+ const missingIds = expected.filter((id) => !coverage[key].includes(id));
484
+ if (missingIds.length > 0) {
485
+ missing.push({ key, ids: missingIds });
486
+ }
487
+ }
488
+ return missing;
489
+ }
490
+ async function ensurePipelinePrereqs(cwd, normalizedPath) {
491
+ const prodoRoot = (0, paths_1.prodoPath)(cwd);
492
+ if (!(await (0, utils_1.fileExists)(prodoRoot))) {
493
+ throw new errors_1.UserError("Missing .prodo directory. Run `prodo-init` first.");
494
+ }
495
+ if (!(await (0, utils_1.fileExists)((0, paths_1.briefPath)(cwd)))) {
496
+ throw new errors_1.UserError("Missing brief at `brief.md`. Run `prodo-init` or create the file.");
497
+ }
498
+ if (!(await (0, utils_1.fileExists)(normalizedPath))) {
499
+ throw new errors_1.UserError("Missing normalized brief at `.prodo/briefs/normalized-brief.json`. Create it before generating artifacts.");
500
+ }
501
+ }
502
+ function splitWorkflowPair(raw) {
503
+ const match = raw.match(/```mermaid\s*([\s\S]*?)```/i);
504
+ if (!match) {
505
+ throw new errors_1.UserError("Workflow output is missing a Mermaid block. Regenerate with template-compliant paired output.");
506
+ }
507
+ const mermaid = match[1].trim();
508
+ const markdown = raw.replace(match[0], "").trim();
509
+ if (!markdown) {
510
+ throw new errors_1.UserError("Workflow markdown explanation is empty.");
511
+ }
512
+ if (!/(^|\n)\s*(flowchart|graph)\s+/i.test(mermaid)) {
513
+ throw new errors_1.UserError("Workflow Mermaid block is invalid.");
514
+ }
515
+ return { markdown, mermaid };
516
+ }
517
+ async function writeWorkflowFlows(targetDir, baseName, normalized, coverage, lang, markdownBody, mermaidBody, mermaidTemplateContent) {
518
+ const targets = workflowFeatureTargets(normalized, coverage);
519
+ const fallbackFeature = normalized.contracts.core_features[0] ?? { id: "F1", text: "Primary flow" };
520
+ const flows = targets.length > 0 ? targets : [fallbackFeature];
521
+ const summaryBodies = [];
522
+ const renderedArtifacts = [];
523
+ let primaryMdPath = "";
524
+ for (const [index, flowFeature] of flows.entries()) {
525
+ const flowBase = flows.length === 1
526
+ ? baseName
527
+ : (index === 0
528
+ ? baseName
529
+ : `${baseName}-${index + 1}-${toSlug(extractTurkishTitle(flowFeature.text))}`);
530
+ const mdPath = node_path_1.default.join(targetDir, `${flowBase}.md`);
531
+ const mmdPath = node_path_1.default.join(targetDir, `${flowBase}.mmd`);
532
+ const featureCoverage = {
533
+ ...coverage,
534
+ core_features: [flowFeature.id]
535
+ };
536
+ const renderedMarkdown = renderWorkflowMarkdownForFeature(markdownBody, flowFeature, lang);
537
+ const renderedMermaid = (mermaidTemplateContent && mermaidTemplateContent.trim().length > 0)
538
+ ? renderWorkflowMermaidTemplate(mermaidTemplateContent, normalized, featureCoverage, lang).trim()
539
+ : (mermaidBody ?? "").trim();
540
+ if (!/(^|\n)\s*(flowchart|graph)\s+/i.test(renderedMermaid)) {
541
+ throw new errors_1.UserError("Workflow Mermaid output is invalid.");
542
+ }
543
+ enforceLanguage(renderedMarkdown, lang, "workflow");
544
+ enforceLanguage(renderedMermaid, lang, "workflow");
545
+ await promises_1.default.writeFile(mdPath, `${renderedMarkdown}\n`, "utf8");
546
+ await promises_1.default.writeFile(mmdPath, `${renderedMermaid}\n`, "utf8");
547
+ if (!primaryMdPath)
548
+ primaryMdPath = mdPath;
549
+ summaryBodies.push(renderedMarkdown);
550
+ renderedArtifacts.push({ mdPath, body: renderedMarkdown });
551
+ }
552
+ return {
553
+ primaryPath: primaryMdPath,
554
+ summaryBody: summaryBodies.join("\n\n"),
555
+ rendered: renderedArtifacts
556
+ };
557
+ }
558
+ async function writeWireframeScreens(targetDir, baseName, normalized, coverage, lang, headings, htmlTemplateContent) {
559
+ const tr = lang.toLowerCase().startsWith("tr");
560
+ const explicitScreens = normalized.contracts.core_features
561
+ .filter((item) => coverage.core_features.includes(item.id))
562
+ .slice(0, 6);
563
+ const screens = explicitScreens.length > 1
564
+ ? explicitScreens
565
+ : (normalized.contracts.core_features.length > 1
566
+ ? normalized.contracts.core_features.slice(0, 6)
567
+ : (explicitScreens.length === 1
568
+ ? explicitScreens
569
+ : normalized.contracts.core_features.slice(0, 1)));
570
+ const summaryBodies = [];
571
+ let primaryMdPath = "";
572
+ for (const [index, screen] of screens.entries()) {
573
+ const title = extractTurkishTitle(screen.text);
574
+ const screenBase = `${baseName}-${index + 1}-${toSlug(title)}`;
575
+ const htmlPath = node_path_1.default.join(targetDir, `${screenBase}.html`);
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>
609
+ </html>`;
610
+ const htmlTemplate = htmlTemplateContent && htmlTemplateContent.trim().length > 0 ? htmlTemplateContent : fallbackHtml;
611
+ const html = replaceTemplateTokens(htmlTemplate, {
612
+ "Screen Title": title,
613
+ "Primary Action": (0, i18n_1.t)("save", lang),
614
+ "Description Label": (0, i18n_1.t)("description", lang),
615
+ "Description Placeholder": `[${screen.id}] ${screen.text}`,
616
+ "Meta Label 1": (0, i18n_1.t)("contract", lang),
617
+ "Meta Value 1": screen.id,
618
+ "Meta Label 2": (0, i18n_1.t)("actor", lang),
619
+ "Meta Value 2": normalized.audience[0] ?? (0, i18n_1.t)("user", lang),
620
+ "Field Label": (0, i18n_1.t)("field", lang),
621
+ "Detailed Input Area": (0, i18n_1.t)("detailed_input_area", lang),
622
+ "Upload / Attachment Area": (0, i18n_1.t)("upload_area", lang),
623
+ "Allowed file types / notes": (0, i18n_1.t)("low_fidelity_wireframe", lang),
624
+ "Consent / confirmation text": (0, i18n_1.t)("confirmation_text", lang)
625
+ }, (token) => {
626
+ const key = token.toLowerCase();
627
+ if (key.includes("screen") || key.includes("title"))
628
+ return title;
629
+ if (key.includes("action") || key.includes("button"))
630
+ return (0, i18n_1.t)("save", lang);
631
+ if (key.includes("field"))
632
+ return (0, i18n_1.t)("field", lang);
633
+ if (key.includes("description") || key.includes("summary"))
634
+ return `[${screen.id}] ${screen.text}`;
635
+ if (key.includes("actor") || key.includes("user"))
636
+ return normalized.audience[0] ?? (0, i18n_1.t)("user", lang);
637
+ if (key.includes("logo"))
638
+ return "[ LOGO ]";
639
+ return token;
640
+ });
641
+ enforceLanguage(html, lang, "wireframe");
642
+ await promises_1.default.writeFile(htmlPath, html, "utf8");
643
+ const defaultMap = {
644
+ purpose: [`- [${screen.id}] ${screen.text}`],
645
+ actor: [`- ${(normalized.audience[0] ?? (0, i18n_1.t)("primary_user", lang))}`],
646
+ sections: [
647
+ `- ${(0, i18n_1.t)("header_and_navigation", lang)}`,
648
+ `- ${(0, i18n_1.t)("content_section", lang)}`,
649
+ `- ${(0, i18n_1.t)("form_section", lang)}`
650
+ ],
651
+ fields: [`- ${(0, i18n_1.t)("text_input", lang)} (field_${index + 1})`],
652
+ actions: [`- ${(0, i18n_1.t)("back", lang)}`, `- ${(0, i18n_1.t)("next", lang)}`, `- ${(0, i18n_1.t)("save", lang)}`],
653
+ states: [`- ${tr ? "Bos durum, yukleniyor, hata, basari" : "Empty, loading, error, success states"}`],
654
+ notes: [`- ${tr ? "Dusuk sadakatli tel kafes taslaktir." : "Low-fidelity black-and-white wireframe mock."}`]
655
+ };
656
+ const fallbackQueue = [
657
+ ...defaultMap.purpose,
658
+ ...defaultMap.actor,
659
+ ...defaultMap.sections,
660
+ ...defaultMap.fields,
661
+ ...defaultMap.actions,
662
+ ...defaultMap.states,
663
+ ...defaultMap.notes
664
+ ];
665
+ const consumeFallback = () => (fallbackQueue.shift() ?? `- ${tr ? "Detay bekleniyor." : "Detail pending."}`);
666
+ const contentForHeading = (heading) => {
667
+ const key = heading.toLowerCase();
668
+ if (/(screen purpose|purpose|amac|hedef)/.test(key))
669
+ return defaultMap.purpose;
670
+ if (/(primary actor|actor|user|kullanici|rol)/.test(key))
671
+ return defaultMap.actor;
672
+ if (/(main section|section|bolum|layout)/.test(key))
673
+ return defaultMap.sections;
674
+ if (/(field|input|form|alan)/.test(key))
675
+ return defaultMap.fields;
676
+ if (/(action|button|cta|aksiyon)/.test(key))
677
+ return defaultMap.actions;
678
+ if (/(state|message|durum|mesaj)/.test(key))
679
+ return defaultMap.states;
680
+ if (/(note|not|aciklama)/.test(key))
681
+ return defaultMap.notes;
682
+ return [consumeFallback()];
683
+ };
684
+ const targetHeadings = headings.length > 0 ? headings : (0, constants_1.defaultRequiredHeadings)("wireframe");
685
+ const mdLines = [`# ${title}`, ""];
686
+ for (const heading of targetHeadings) {
687
+ mdLines.push(heading);
688
+ mdLines.push(...contentForHeading(heading));
689
+ mdLines.push("");
690
+ }
691
+ const mdBody = mdLines.join("\n").trim();
692
+ enforceLanguage(mdBody, lang, "wireframe");
693
+ await promises_1.default.writeFile(mdPath, `${mdBody}\n`, "utf8");
694
+ if (!primaryMdPath)
695
+ primaryMdPath = mdPath;
696
+ summaryBodies.push(mdBody);
697
+ }
698
+ return {
699
+ primaryPath: primaryMdPath,
700
+ summaryBody: summaryBodies.join("\n\n")
701
+ };
702
+ }
703
+ async function generateArtifact(options) {
704
+ const { cwd, artifactType, outPath, agent } = options;
705
+ const revisionType = options.revisionType ?? "default";
706
+ const def = await (0, artifact_registry_1.getArtifactDefinition)(cwd, artifactType);
707
+ const normalizedPath = options.normalizedBriefOverride ?? (0, paths_1.normalizedBriefPath)(cwd);
708
+ await ensurePipelinePrereqs(cwd, normalizedPath);
709
+ const documentControlVersion = await resolveDocumentControlVersion(cwd, artifactType, revisionType);
710
+ const settings = await (0, settings_1.readSettings)(cwd);
711
+ const normalizedBriefRaw = await (0, utils_1.readJsonFile)(normalizedPath);
712
+ const normalizedBrief = (0, normalized_brief_1.parseNormalizedBriefOrThrow)(normalizedBriefRaw);
713
+ const template = await (0, template_resolver_1.resolveTemplate)({ cwd, artifactType });
714
+ const companionTemplate = await (0, template_resolver_1.resolveCompanionTemplate)({ cwd, artifactType });
715
+ if (!template || template.content.trim().length === 0) {
716
+ throw new errors_1.UserError(`Missing ${artifactType} template. Create \`.prodo/templates/${artifactType}.md\` before running \`prodo-${artifactType}\`.`);
717
+ }
718
+ if (artifactType === "workflow" && !companionTemplate) {
719
+ throw new errors_1.UserError("Missing workflow companion template. Create `.prodo/templates/workflow.mmd` before running `prodo-workflow`.");
720
+ }
721
+ if (artifactType === "wireframe" && !companionTemplate) {
722
+ throw new errors_1.UserError("Missing wireframe companion template. Create `.prodo/templates/wireframe.html` before running `prodo-wireframe`.");
723
+ }
724
+ const templateHeadings = template && template.content.trim().length > 0 ? (0, template_resolver_1.extractRequiredHeadingsFromTemplate)(template.content) : [];
725
+ if (templateHeadings.length === 0) {
726
+ throw new errors_1.UserError(`${artifactType} template has no extractable headings. Add markdown headings to \`${template.path}\`.`);
727
+ }
728
+ const computedHeadings = templateHeadings.length > 0
729
+ ? templateHeadings
730
+ : (def.required_headings.length > 0 ? def.required_headings : (0, constants_1.defaultRequiredHeadings)(artifactType));
731
+ const prompt = await resolvePrompt(cwd, artifactType, template?.content ?? "", computedHeadings, companionTemplate, settings.author, agent);
732
+ const provider = (0, providers_1.createProvider)();
733
+ const upstreamArtifacts = await buildUpstreamArtifacts(cwd, artifactType, def.upstream);
734
+ const schemaHint = {
735
+ artifactType,
736
+ requiredHeadings: computedHeadings,
737
+ requiredContracts: def.required_contracts
738
+ };
739
+ const generated = await provider.generate(prompt, {
740
+ normalizedBrief,
741
+ upstreamArtifacts,
742
+ contractCatalog: normalizedBrief.contracts,
743
+ templateContent: template?.content ?? "",
744
+ templatePath: template?.path ?? "",
745
+ companionTemplateContent: companionTemplate?.content ?? "",
746
+ companionTemplatePath: companionTemplate?.path ?? "",
747
+ outputLanguage: settings.lang,
748
+ outputAuthor: settings.author
749
+ }, schemaHint);
750
+ let generatedBody = enforceAuthorInControlTables(replaceAuthorPlaceholders(generated.body.trim(), settings.author), settings.author);
751
+ let workflowMermaidBody = null;
752
+ if (artifactType === "workflow") {
753
+ const paired = splitWorkflowPair(generatedBody);
754
+ generatedBody = enforceAuthorInControlTables(replaceAuthorPlaceholders(paired.markdown, settings.author), settings.author);
755
+ workflowMermaidBody = replaceAuthorPlaceholders(paired.mermaid, settings.author);
756
+ }
757
+ let contractCoverage = extractCoverageFromBody(generatedBody);
758
+ if (artifactType === "workflow") {
759
+ if (contractCoverage.core_features.length === 0) {
760
+ contractCoverage = {
761
+ ...contractCoverage,
762
+ core_features: normalizedBrief.contracts.core_features.map((item) => item.id)
763
+ };
764
+ }
765
+ }
766
+ if (artifactType === "wireframe") {
767
+ if (contractCoverage.core_features.length === 0) {
768
+ contractCoverage = {
769
+ ...contractCoverage,
770
+ core_features: normalizedBrief.contracts.core_features.map((item) => item.id)
771
+ };
772
+ }
773
+ }
774
+ generatedBody = applyDocumentControlDefaults(generatedBody, {
775
+ lang: settings.lang,
776
+ revisionType,
777
+ version: documentControlVersion,
778
+ author: settings.author
779
+ });
780
+ if (artifactType === "workflow" && companionTemplate?.content) {
781
+ workflowMermaidBody = renderWorkflowMermaidTemplate(companionTemplate.content, normalizedBrief, contractCoverage, settings.lang).trim();
782
+ workflowMermaidBody = replaceAuthorPlaceholders(workflowMermaidBody, settings.author);
783
+ }
784
+ enforceLanguage(generatedBody, settings.lang, artifactType);
785
+ if (artifactType === "workflow" && workflowMermaidBody) {
786
+ enforceLanguage(workflowMermaidBody, settings.lang, artifactType);
787
+ }
788
+ const uncovered = missingCoverage(def.required_contracts, normalizedBrief, contractCoverage);
789
+ if (uncovered.length > 0) {
790
+ const lines = uncovered
791
+ .map((item) => `- ${item.key}: missing ${item.ids.join(", ")}`)
792
+ .join("\n");
793
+ throw new errors_1.UserError(`Artifact is missing required contract references. Add ID tags to body:\n${lines}\nExample tags: [G1], [F2], [C1].`);
794
+ }
795
+ const frontmatter = {
796
+ artifact_type: artifactType,
797
+ version: (0, utils_1.timestampSlug)(),
798
+ source_brief: node_path_1.default.resolve(normalizedPath),
799
+ generated_at: new Date().toISOString(),
800
+ status: constants_1.DEFAULT_STATUS,
801
+ upstream_artifacts: upstreamArtifacts.map((item) => item.file),
802
+ contract_coverage: contractCoverage,
803
+ language: settings.lang,
804
+ ...(normalizeAuthor(settings.author) ? { author: normalizeAuthor(settings.author) } : {})
805
+ };
806
+ const mergedFrontmatter = { ...frontmatter, ...(generated.frontmatter ?? {}) };
807
+ if (normalizeAuthor(settings.author)) {
808
+ mergedFrontmatter.author = normalizeAuthor(settings.author);
809
+ }
810
+ let doc = {
811
+ frontmatter: mergedFrontmatter,
812
+ body: generatedBody
813
+ };
814
+ const validation = await (0, validator_1.validateSchema)(cwd, artifactType, doc, schemaHint.requiredHeadings);
815
+ const schemaErrors = validation.issues.filter((issue) => issue.level === "error");
816
+ if (schemaErrors.length > 0) {
817
+ const details = schemaErrors.map((issue) => `- ${issue.message}`).join("\n");
818
+ throw new errors_1.UserError(`Artifact failed schema checks:\n${details}`);
819
+ }
820
+ const targetDir = (0, paths_1.outputDirPath)(cwd, artifactType, def.output_dir);
821
+ const finalPath = outPath
822
+ ? node_path_1.default.resolve(cwd, outPath)
823
+ : await resolveUniqueOutputPath(targetDir, defaultFilename(artifactType));
824
+ if (!(0, utils_1.isPathInside)(node_path_1.default.join(cwd, "product-docs"), finalPath)) {
825
+ throw new errors_1.UserError("Artifact output must be inside `product-docs/`.");
826
+ }
827
+ await promises_1.default.mkdir(node_path_1.default.dirname(finalPath), { recursive: true });
828
+ if (artifactType === "workflow") {
829
+ const basePath = node_path_1.default.join(node_path_1.default.dirname(finalPath), node_path_1.default.parse(finalPath).name);
830
+ const workflow = await writeWorkflowFlows(node_path_1.default.dirname(basePath), node_path_1.default.parse(basePath).name, normalizedBrief, contractCoverage, settings.lang, doc.body, workflowMermaidBody, companionTemplate?.content ?? null);
831
+ await promises_1.default.mkdir((0, paths_1.outputContextDirPath)(cwd), { recursive: true });
832
+ for (const rendered of workflow.rendered) {
833
+ const renderedDoc = {
834
+ frontmatter: doc.frontmatter,
835
+ body: rendered.body
836
+ };
837
+ await promises_1.default.writeFile(rendered.mdPath, gray_matter_1.default.stringify(renderedDoc.body, renderedDoc.frontmatter), "utf8");
838
+ await writeSidecar(rendered.mdPath, renderedDoc);
839
+ const renderedContext = {
840
+ artifact_type: artifactType,
841
+ artifact_file: rendered.mdPath,
842
+ generated_at: new Date().toISOString(),
843
+ contract_coverage: contractCoverage,
844
+ ...deriveStructuredContext(artifactType, renderedDoc.body, schemaHint.requiredHeadings)
845
+ };
846
+ await promises_1.default.writeFile(contextFilePath(cwd, rendered.mdPath), `${JSON.stringify(renderedContext, null, 2)}\n`, "utf8");
847
+ }
848
+ const primaryRendered = workflow.rendered.find((item) => item.mdPath === workflow.primaryPath) ?? workflow.rendered[0];
849
+ doc = {
850
+ frontmatter: doc.frontmatter,
851
+ body: primaryRendered?.body ?? doc.body
852
+ };
853
+ await (0, output_index_1.setActiveArtifact)(cwd, artifactType, workflow.primaryPath);
854
+ return workflow.primaryPath;
855
+ }
856
+ else if (artifactType === "wireframe") {
857
+ const base = node_path_1.default.parse(finalPath).name;
858
+ const wireframe = await writeWireframeScreens(node_path_1.default.dirname(finalPath), base, normalizedBrief, contractCoverage, settings.lang, schemaHint.requiredHeadings, companionTemplate?.content ?? null);
859
+ doc = {
860
+ frontmatter: doc.frontmatter,
861
+ body: wireframe.summaryBody
862
+ };
863
+ await writeSidecar(wireframe.primaryPath, doc);
864
+ const derivedContext = {
865
+ artifact_type: artifactType,
866
+ artifact_file: wireframe.primaryPath,
867
+ generated_at: new Date().toISOString(),
868
+ contract_coverage: contractCoverage,
869
+ ...deriveStructuredContext(artifactType, doc.body, schemaHint.requiredHeadings)
870
+ };
871
+ await promises_1.default.mkdir((0, paths_1.outputContextDirPath)(cwd), { recursive: true });
872
+ await promises_1.default.writeFile(contextFilePath(cwd, wireframe.primaryPath), `${JSON.stringify(derivedContext, null, 2)}\n`, "utf8");
873
+ await (0, output_index_1.setActiveArtifact)(cwd, artifactType, wireframe.primaryPath);
874
+ return wireframe.primaryPath;
875
+ }
876
+ else {
877
+ const content = gray_matter_1.default.stringify(doc.body, doc.frontmatter);
878
+ await promises_1.default.writeFile(finalPath, content, "utf8");
879
+ }
880
+ await writeSidecar(finalPath, doc);
881
+ const derivedContext = {
882
+ artifact_type: artifactType,
883
+ artifact_file: finalPath,
884
+ generated_at: new Date().toISOString(),
885
+ contract_coverage: contractCoverage,
886
+ ...deriveStructuredContext(artifactType, doc.body, schemaHint.requiredHeadings)
887
+ };
888
+ await promises_1.default.mkdir((0, paths_1.outputContextDirPath)(cwd), { recursive: true });
889
+ await promises_1.default.writeFile(contextFilePath(cwd, finalPath), `${JSON.stringify(derivedContext, null, 2)}\n`, "utf8");
890
+ await (0, output_index_1.setActiveArtifact)(cwd, artifactType, finalPath);
891
+ return finalPath;
892
+ }