@shahmarasy/prodo 0.1.3 → 0.1.5

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/agents.js +4 -2
  18. package/dist/artifacts.d.ts +1 -0
  19. package/dist/artifacts.js +265 -31
  20. package/dist/cli/agent-command-installer.d.ts +4 -0
  21. package/dist/cli/agent-command-installer.js +148 -0
  22. package/dist/cli/agent-ids.d.ts +15 -0
  23. package/dist/cli/agent-ids.js +49 -0
  24. package/dist/cli/doctor.d.ts +1 -0
  25. package/dist/cli/doctor.js +144 -0
  26. package/dist/cli/fix-tui.d.ts +4 -0
  27. package/dist/cli/fix-tui.js +79 -0
  28. package/dist/cli/index.d.ts +9 -0
  29. package/dist/cli/index.js +465 -0
  30. package/dist/cli/init-tui.d.ts +23 -0
  31. package/dist/cli/init-tui.js +176 -0
  32. package/dist/cli/init.d.ts +11 -0
  33. package/dist/cli/init.js +334 -0
  34. package/dist/cli/normalize-interactive.d.ts +8 -0
  35. package/dist/cli/normalize-interactive.js +167 -0
  36. package/dist/cli/preset-loader.d.ts +4 -0
  37. package/dist/cli/preset-loader.js +210 -0
  38. package/dist/cli.js +80 -3
  39. package/dist/core/artifact-registry.d.ts +11 -0
  40. package/dist/core/artifact-registry.js +49 -0
  41. package/dist/core/artifacts.d.ts +10 -0
  42. package/dist/core/artifacts.js +892 -0
  43. package/dist/core/clean.d.ts +10 -0
  44. package/dist/core/clean.js +74 -0
  45. package/dist/core/consistency.d.ts +8 -0
  46. package/dist/core/consistency.js +328 -0
  47. package/dist/core/constants.d.ts +7 -0
  48. package/dist/core/constants.js +64 -0
  49. package/dist/core/errors.d.ts +3 -0
  50. package/dist/core/errors.js +10 -0
  51. package/dist/core/fix.d.ts +31 -0
  52. package/dist/core/fix.js +188 -0
  53. package/dist/core/hook-executor.d.ts +1 -0
  54. package/dist/core/hook-executor.js +175 -0
  55. package/dist/core/markdown.d.ts +16 -0
  56. package/dist/core/markdown.js +81 -0
  57. package/dist/core/normalize.d.ts +8 -0
  58. package/dist/core/normalize.js +125 -0
  59. package/dist/core/normalized-brief.d.ts +48 -0
  60. package/dist/core/normalized-brief.js +182 -0
  61. package/dist/core/output-index.d.ts +13 -0
  62. package/dist/core/output-index.js +55 -0
  63. package/dist/core/paths.d.ts +17 -0
  64. package/dist/core/paths.js +80 -0
  65. package/dist/core/project-config.d.ts +14 -0
  66. package/dist/core/project-config.js +69 -0
  67. package/dist/core/registry.d.ts +13 -0
  68. package/dist/core/registry.js +115 -0
  69. package/dist/core/settings.d.ts +7 -0
  70. package/dist/core/settings.js +35 -0
  71. package/dist/core/template-engine.d.ts +3 -0
  72. package/dist/core/template-engine.js +43 -0
  73. package/dist/core/template-resolver.d.ts +15 -0
  74. package/dist/core/template-resolver.js +46 -0
  75. package/dist/core/templates.d.ts +33 -0
  76. package/dist/core/templates.js +440 -0
  77. package/dist/core/terminology.d.ts +21 -0
  78. package/dist/core/terminology.js +143 -0
  79. package/dist/core/tracing.d.ts +21 -0
  80. package/dist/core/tracing.js +74 -0
  81. package/dist/core/types.d.ts +35 -0
  82. package/dist/core/types.js +5 -0
  83. package/dist/core/utils.d.ts +7 -0
  84. package/dist/core/utils.js +66 -0
  85. package/dist/core/validate.d.ts +10 -0
  86. package/dist/core/validate.js +226 -0
  87. package/dist/core/validator.d.ts +5 -0
  88. package/dist/core/validator.js +76 -0
  89. package/dist/core/version.d.ts +1 -0
  90. package/dist/core/version.js +30 -0
  91. package/dist/core/workflow-commands.d.ts +7 -0
  92. package/dist/core/workflow-commands.js +29 -0
  93. package/dist/i18n/en.json +45 -0
  94. package/dist/i18n/index.d.ts +5 -0
  95. package/dist/i18n/index.js +63 -0
  96. package/dist/i18n/tr.json +45 -0
  97. package/dist/init-tui.d.ts +3 -0
  98. package/dist/init-tui.js +28 -1
  99. package/dist/init.d.ts +1 -0
  100. package/dist/init.js +9 -3
  101. package/dist/normalize.js +55 -7
  102. package/dist/providers/index.d.ts +2 -1
  103. package/dist/providers/index.js +20 -6
  104. package/dist/providers/mock-provider.d.ts +1 -1
  105. package/dist/providers/mock-provider.js +7 -6
  106. package/dist/providers/openai-provider.d.ts +1 -1
  107. package/dist/providers/openai-provider.js +3 -2
  108. package/dist/settings.d.ts +1 -0
  109. package/dist/settings.js +2 -1
  110. package/dist/skills/engine.d.ts +10 -0
  111. package/dist/skills/engine.js +75 -0
  112. package/dist/skills/fix-skill.d.ts +2 -0
  113. package/dist/skills/fix-skill.js +38 -0
  114. package/dist/skills/generate-artifact-skill.d.ts +2 -0
  115. package/dist/skills/generate-artifact-skill.js +32 -0
  116. package/dist/skills/generate-pipeline-skill.d.ts +2 -0
  117. package/dist/skills/generate-pipeline-skill.js +45 -0
  118. package/dist/skills/normalize-skill.d.ts +2 -0
  119. package/dist/skills/normalize-skill.js +29 -0
  120. package/dist/skills/types.d.ts +28 -0
  121. package/dist/skills/types.js +2 -0
  122. package/dist/skills/validate-skill.d.ts +2 -0
  123. package/dist/skills/validate-skill.js +29 -0
  124. package/dist/templates.d.ts +1 -1
  125. package/dist/templates.js +2 -0
  126. package/dist/utils.d.ts +1 -0
  127. package/dist/utils.js +13 -0
  128. package/dist/validator.js +0 -4
  129. package/dist/workflow-commands.js +2 -1
  130. package/package.json +74 -45
  131. package/presets/fintech/preset.json +48 -1
  132. package/presets/fintech/prompts/prd.md +99 -2
  133. package/presets/marketplace/preset.json +51 -1
  134. package/presets/marketplace/prompts/prd.md +140 -2
  135. package/presets/saas/preset.json +53 -1
  136. package/presets/saas/prompts/prd.md +150 -2
  137. package/src/agents/agent-registry.ts +93 -0
  138. package/src/agents/anthropic/index.ts +86 -0
  139. package/src/agents/anthropic/manifest.json +7 -0
  140. package/src/agents/base.ts +77 -0
  141. package/src/agents/google/index.ts +79 -0
  142. package/src/agents/google/manifest.json +7 -0
  143. package/src/agents/mock/index.ts +32 -0
  144. package/src/agents/mock/manifest.json +7 -0
  145. package/src/agents/openai/index.ts +83 -0
  146. package/src/agents/openai/manifest.json +7 -0
  147. package/src/agents/system-prompts.ts +35 -0
  148. package/src/{agent-command-installer.ts → cli/agent-command-installer.ts} +164 -164
  149. package/src/{agents.ts → cli/agent-ids.ts} +58 -56
  150. package/src/{doctor.ts → cli/doctor.ts} +157 -137
  151. package/src/cli/fix-tui.ts +111 -0
  152. package/src/{cli.ts → cli/index.ts} +459 -319
  153. package/src/{init-tui.ts → cli/init-tui.ts} +208 -179
  154. package/src/{init.ts → cli/init.ts} +398 -391
  155. package/src/cli/normalize-interactive.ts +241 -0
  156. package/src/{preset-loader.ts → cli/preset-loader.ts} +237 -237
  157. package/src/{artifact-registry.ts → core/artifact-registry.ts} +69 -69
  158. package/src/{artifacts.ts → core/artifacts.ts} +1081 -777
  159. package/src/core/clean.ts +88 -0
  160. package/src/{consistency.ts → core/consistency.ts} +374 -303
  161. package/src/{constants.ts → core/constants.ts} +72 -72
  162. package/src/{errors.ts → core/errors.ts} +7 -7
  163. package/src/core/fix.ts +253 -0
  164. package/src/{hook-executor.ts → core/hook-executor.ts} +196 -196
  165. package/src/{markdown.ts → core/markdown.ts} +93 -73
  166. package/src/core/normalize.ts +145 -0
  167. package/src/{normalized-brief.ts → core/normalized-brief.ts} +227 -206
  168. package/src/{output-index.ts → core/output-index.ts} +59 -59
  169. package/src/{paths.ts → core/paths.ts} +75 -71
  170. package/src/{project-config.ts → core/project-config.ts} +78 -78
  171. package/src/{registry.ts → core/registry.ts} +119 -119
  172. package/src/{settings.ts → core/settings.ts} +35 -34
  173. package/src/core/template-engine.ts +45 -0
  174. package/src/{template-resolver.ts → core/template-resolver.ts} +54 -54
  175. package/src/{templates.ts → core/templates.ts} +452 -450
  176. package/src/core/terminology.ts +177 -0
  177. package/src/core/tracing.ts +110 -0
  178. package/src/{types.ts → core/types.ts} +46 -46
  179. package/src/{utils.ts → core/utils.ts} +64 -50
  180. package/src/{validate.ts → core/validate.ts} +252 -246
  181. package/src/{validator.ts → core/validator.ts} +92 -96
  182. package/src/{version.ts → core/version.ts} +24 -24
  183. package/src/{workflow-commands.ts → core/workflow-commands.ts} +32 -31
  184. package/src/i18n/en.json +45 -0
  185. package/src/i18n/index.ts +58 -0
  186. package/src/i18n/tr.json +45 -0
  187. package/src/providers/index.ts +29 -12
  188. package/src/providers/mock-provider.ts +200 -199
  189. package/src/providers/openai-provider.ts +88 -87
  190. package/src/skills/engine.ts +94 -0
  191. package/src/skills/fix-skill.ts +38 -0
  192. package/src/skills/generate-artifact-skill.ts +32 -0
  193. package/src/skills/generate-pipeline-skill.ts +49 -0
  194. package/src/skills/normalize-skill.ts +29 -0
  195. package/src/skills/types.ts +36 -0
  196. package/src/skills/validate-skill.ts +29 -0
  197. package/templates/commands/prodo-fix.md +46 -0
  198. package/templates/commands/prodo-normalize.md +118 -23
  199. package/templates/commands/prodo-prd.md +138 -17
  200. package/templates/commands/prodo-stories.md +153 -17
  201. package/templates/commands/prodo-techspec.md +167 -17
  202. package/templates/commands/prodo-validate.md +184 -26
  203. package/templates/commands/prodo-wireframe.md +188 -17
  204. package/templates/commands/prodo-workflow.md +200 -17
  205. package/src/normalize.ts +0 -89
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GoogleAgent = void 0;
4
+ const base_1 = require("../base");
5
+ const system_prompts_1 = require("../system-prompts");
6
+ const errors_1 = require("../../core/errors");
7
+ const dynamicImport = new Function("specifier", "return import(specifier)");
8
+ class GoogleAgent extends base_1.BaseAgent {
9
+ name = "google";
10
+ displayName = "Google Gemini";
11
+ sdkRequired = "@google/generative-ai";
12
+ getConfig() {
13
+ return {
14
+ name: this.name,
15
+ displayName: this.displayName,
16
+ sdkRequired: this.sdkRequired,
17
+ envVars: ["GOOGLE_API_KEY"],
18
+ defaultModel: "gemini-2.0-flash"
19
+ };
20
+ }
21
+ async generate(prompt, inputContext, schemaHint) {
22
+ const apiKey = process.env.GOOGLE_API_KEY;
23
+ if (!apiKey) {
24
+ throw new errors_1.UserError("GOOGLE_API_KEY is not set. Set it to use the Google Gemini agent.");
25
+ }
26
+ const model = process.env.PRODO_GOOGLE_MODEL ?? "gemini-2.0-flash";
27
+ const outputLanguage = typeof inputContext.outputLanguage === "string" && inputContext.outputLanguage.trim()
28
+ ? inputContext.outputLanguage.trim()
29
+ : "en";
30
+ const system = (0, system_prompts_1.buildSystemPrompt)(schemaHint, outputLanguage);
31
+ const user = (0, system_prompts_1.buildUserMessage)(prompt, inputContext);
32
+ let GoogleGenerativeAI;
33
+ try {
34
+ const mod = (await dynamicImport("@google/generative-ai"));
35
+ GoogleGenerativeAI = mod.GoogleGenerativeAI;
36
+ }
37
+ catch {
38
+ throw new errors_1.UserError("Google Generative AI SDK is not installed. Run: npm install @google/generative-ai");
39
+ }
40
+ const client = new GoogleGenerativeAI(apiKey);
41
+ const generativeModel = client.getGenerativeModel({
42
+ model,
43
+ systemInstruction: system
44
+ });
45
+ const result = await generativeModel.generateContent(user);
46
+ const content = result.response.text().trim();
47
+ if (!content) {
48
+ throw new errors_1.UserError("Google Gemini agent returned an empty response.");
49
+ }
50
+ return { body: content };
51
+ }
52
+ }
53
+ exports.GoogleAgent = GoogleAgent;
@@ -0,0 +1,11 @@
1
+ import { BaseAgent, type AgentConfig } from "../base";
2
+ import type { GenerateResult, ProviderSchemaHint } from "../../core/types";
3
+ export declare class MockAgent extends BaseAgent {
4
+ readonly name = "mock";
5
+ readonly displayName = "Mock (Testing)";
6
+ readonly sdkRequired: null;
7
+ private readonly provider;
8
+ generate(prompt: string, inputContext: Record<string, unknown>, schemaHint: ProviderSchemaHint): Promise<GenerateResult>;
9
+ isAvailable(): Promise<boolean>;
10
+ getConfig(): AgentConfig;
11
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MockAgent = void 0;
4
+ const base_1 = require("../base");
5
+ const mock_provider_1 = require("../../providers/mock-provider");
6
+ class MockAgent extends base_1.BaseAgent {
7
+ name = "mock";
8
+ displayName = "Mock (Testing)";
9
+ sdkRequired = null;
10
+ provider = new mock_provider_1.MockProvider();
11
+ async generate(prompt, inputContext, schemaHint) {
12
+ return this.provider.generate(prompt, inputContext, schemaHint);
13
+ }
14
+ async isAvailable() {
15
+ return true;
16
+ }
17
+ getConfig() {
18
+ return {
19
+ name: this.name,
20
+ displayName: this.displayName,
21
+ sdkRequired: null,
22
+ envVars: []
23
+ };
24
+ }
25
+ }
26
+ exports.MockAgent = MockAgent;
@@ -0,0 +1,9 @@
1
+ import { BaseAgent, type AgentConfig } from "../base";
2
+ import type { GenerateResult, ProviderSchemaHint } from "../../core/types";
3
+ export declare class OpenAIAgent extends BaseAgent {
4
+ readonly name = "openai";
5
+ readonly displayName = "OpenAI";
6
+ readonly sdkRequired = "openai";
7
+ getConfig(): AgentConfig;
8
+ generate(prompt: string, inputContext: Record<string, unknown>, schemaHint: ProviderSchemaHint): Promise<GenerateResult>;
9
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenAIAgent = void 0;
4
+ const base_1 = require("../base");
5
+ const system_prompts_1 = require("../system-prompts");
6
+ const errors_1 = require("../../core/errors");
7
+ const dynamicImport = new Function("specifier", "return import(specifier)");
8
+ class OpenAIAgent extends base_1.BaseAgent {
9
+ name = "openai";
10
+ displayName = "OpenAI";
11
+ sdkRequired = "openai";
12
+ getConfig() {
13
+ return {
14
+ name: this.name,
15
+ displayName: this.displayName,
16
+ sdkRequired: this.sdkRequired,
17
+ envVars: ["OPENAI_API_KEY"],
18
+ defaultModel: "gpt-4o-mini"
19
+ };
20
+ }
21
+ async generate(prompt, inputContext, schemaHint) {
22
+ const apiKey = process.env.OPENAI_API_KEY;
23
+ if (!apiKey) {
24
+ throw new errors_1.UserError("OPENAI_API_KEY is not set. Set it to use the OpenAI agent.");
25
+ }
26
+ const model = process.env.PRODO_OPENAI_MODEL ?? "gpt-4o-mini";
27
+ const baseURL = process.env.PRODO_OPENAI_BASE_URL ?? undefined;
28
+ const outputLanguage = typeof inputContext.outputLanguage === "string" && inputContext.outputLanguage.trim()
29
+ ? inputContext.outputLanguage.trim()
30
+ : "en";
31
+ const system = (0, system_prompts_1.buildSystemPrompt)(schemaHint, outputLanguage);
32
+ const user = (0, system_prompts_1.buildUserMessage)(prompt, inputContext);
33
+ let OpenAIConstructor;
34
+ try {
35
+ const mod = (await dynamicImport("openai"));
36
+ OpenAIConstructor = mod.default;
37
+ }
38
+ catch {
39
+ throw new errors_1.UserError("OpenAI SDK is not installed. Run: npm install openai");
40
+ }
41
+ const client = new OpenAIConstructor({ apiKey, baseURL });
42
+ const response = await client.chat.completions.create({
43
+ model,
44
+ messages: [
45
+ { role: "system", content: system },
46
+ { role: "user", content: user }
47
+ ],
48
+ temperature: 0.2
49
+ });
50
+ const content = response.choices[0]?.message?.content?.trim();
51
+ if (!content) {
52
+ throw new errors_1.UserError("OpenAI agent returned an empty response.");
53
+ }
54
+ return { body: content };
55
+ }
56
+ }
57
+ exports.OpenAIAgent = OpenAIAgent;
@@ -0,0 +1,3 @@
1
+ import type { ProviderSchemaHint } from "../core/types";
2
+ export declare function buildSystemPrompt(schemaHint: ProviderSchemaHint, outputLanguage: string): string;
3
+ export declare function buildUserMessage(prompt: string, inputContext: Record<string, unknown>): string;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildSystemPrompt = buildSystemPrompt;
4
+ exports.buildUserMessage = buildUserMessage;
5
+ function buildSystemPrompt(schemaHint, outputLanguage) {
6
+ const mode = schemaHint.artifactType;
7
+ if (mode === "normalize") {
8
+ return `You normalize messy human product briefs into strict JSON.
9
+ Return valid JSON only, no markdown. Include confidence scores (0..1) for critical fields.
10
+ Preserve source language and Unicode characters exactly; never transliterate Turkish letters to ASCII.`;
11
+ }
12
+ if (mode === "semantic_consistency") {
13
+ return `You detect semantic inconsistencies between paired artifacts.
14
+ Return valid JSON only: { "issues": [{level, code, check, contract_id, file, message, suggestion}] }.`;
15
+ }
16
+ if (mode === "contract_relevance") {
17
+ return `You verify whether tagged content actually matches the referenced contract text.
18
+ Return valid JSON only: { "relevant": boolean, "score": number, "reason": string }.`;
19
+ }
20
+ return `You are a product-document generator.
21
+ Return only Markdown body content.
22
+ Headings required:
23
+ ${schemaHint.requiredHeadings.join("\n")}
24
+ Required contract tags:
25
+ ${schemaHint.requiredContracts.join(", ")}
26
+ Use tags like [G1], [F2], [C1] where relevant.
27
+ Output language: ${outputLanguage}
28
+ Do not translate required headings.`;
29
+ }
30
+ function buildUserMessage(prompt, inputContext) {
31
+ return `${prompt}\n\nContext JSON:\n${JSON.stringify(inputContext, null, 2)}`;
32
+ }
package/dist/agents.js CHANGED
@@ -32,7 +32,8 @@ async function loadAgentCommandSet(_cwd, agent) {
32
32
  { command: `${prefix}-wireframe`, purpose: "Generate wireframe artifact." },
33
33
  { command: `${prefix}-stories`, purpose: "Generate stories artifact." },
34
34
  { command: `${prefix}-techspec`, purpose: "Generate techspec artifact." },
35
- { command: `${prefix}-validate`, purpose: "Run validation report." }
35
+ { command: `${prefix}-validate`, purpose: "Run validation report." },
36
+ { command: `${prefix}-fix`, purpose: "Fix artifacts when validation fails." }
36
37
  ],
37
38
  artifact_shortcuts: {
38
39
  normalize: `${prefix}-normalize`,
@@ -41,7 +42,8 @@ async function loadAgentCommandSet(_cwd, agent) {
41
42
  wireframe: `${prefix}-wireframe`,
42
43
  stories: `${prefix}-stories`,
43
44
  techspec: `${prefix}-techspec`,
44
- validate: `${prefix}-validate`
45
+ validate: `${prefix}-validate`,
46
+ fix: `${prefix}-fix`
45
47
  }
46
48
  };
47
49
  }
@@ -5,5 +5,6 @@ export type GenerateOptions = {
5
5
  normalizedBriefOverride?: string;
6
6
  outPath?: string;
7
7
  agent?: string;
8
+ revisionType?: "default" | "fix";
8
9
  };
9
10
  export declare function generateArtifact(options: GenerateOptions): Promise<string>;
package/dist/artifacts.js CHANGED
@@ -20,11 +20,7 @@ const markdown_1 = require("./markdown");
20
20
  const utils_1 = require("./utils");
21
21
  const validator_1 = require("./validator");
22
22
  function defaultFilename(type) {
23
- if (type === "workflow")
24
- return `${type}-${(0, utils_1.timestampSlug)()}.md`;
25
- if (type === "wireframe")
26
- return `${type}-${(0, utils_1.timestampSlug)()}.md`;
27
- return `${type}-${(0, utils_1.timestampSlug)()}.md`;
23
+ return `${type}-${(0, utils_1.artifactFileStamp)()}.md`;
28
24
  }
29
25
  function sidecarPath(filePath) {
30
26
  const parsed = node_path_1.default.parse(filePath);
@@ -147,7 +143,166 @@ function renderWorkflowMermaidTemplate(templateContent, normalized, coverage, la
147
143
  return token;
148
144
  });
149
145
  }
150
- async function resolvePrompt(cwd, artifactType, templateContent, requiredHeadings, companionTemplate, agent) {
146
+ function normalizeAuthor(author) {
147
+ if (!author)
148
+ return undefined;
149
+ const normalized = author.trim();
150
+ return normalized.length > 0 ? normalized : undefined;
151
+ }
152
+ function replaceAuthorPlaceholders(body, author) {
153
+ const safeAuthor = normalizeAuthor(author);
154
+ if (!safeAuthor)
155
+ return body;
156
+ return body.replace(/\{\{\s*author\s*\}\}/gi, safeAuthor);
157
+ }
158
+ function todayYmd() {
159
+ const now = new Date();
160
+ const y = now.getFullYear();
161
+ const m = String(now.getMonth() + 1).padStart(2, "0");
162
+ const d = String(now.getDate()).padStart(2, "0");
163
+ return `${y}-${m}-${d}`;
164
+ }
165
+ function headingKey(value) {
166
+ return value
167
+ .toLowerCase()
168
+ .replace(/[^a-z0-9]+/g, " ")
169
+ .trim();
170
+ }
171
+ function defaultDocumentControlValues(lang, revisionType, version, author) {
172
+ const tr = lang.toLowerCase().startsWith("tr");
173
+ const safeAuthor = normalizeAuthor(author) ?? (tr ? "Prodo" : "Prodo");
174
+ const description = revisionType === "fix"
175
+ ? (tr ? "Dogrulama sonrasi duzeltme revizyonu" : "Post-validation fix revision")
176
+ : (tr ? "Ilk surum" : "Initial version");
177
+ return {
178
+ version,
179
+ date: todayYmd(),
180
+ author: safeAuthor,
181
+ description
182
+ };
183
+ }
184
+ function applyDocumentControlDefaults(body, options) {
185
+ const defaults = defaultDocumentControlValues(options.lang, options.revisionType, options.version, options.author);
186
+ let out = body
187
+ .replace(/\{\{\s*date\s*\}\}/gi, defaults.date)
188
+ .replace(/\{\{\s*description\s*\}\}/gi, defaults.description)
189
+ .replace(/\{\{\s*version\s*\}\}/gi, defaults.version);
190
+ const lines = out.split(/\r?\n/);
191
+ const headingIndex = lines.findIndex((line) => {
192
+ const match = line.match(/^\s*##+\s+(.+?)\s*$/);
193
+ if (!match)
194
+ return false;
195
+ const key = headingKey(match[1]);
196
+ return key.includes("document control") || key.includes("belge kontrol");
197
+ });
198
+ if (headingIndex === -1)
199
+ return out;
200
+ const row = `| ${defaults.version} | ${defaults.date} | ${defaults.author} | ${defaults.description} |`;
201
+ let tableSeparatorIndex = -1;
202
+ let tableDataIndex = -1;
203
+ for (let i = headingIndex + 1; i < lines.length; i += 1) {
204
+ if (/^\s*##+\s+/.test(lines[i]))
205
+ break;
206
+ if (tableSeparatorIndex === -1 && /\|/.test(lines[i]) && /-/.test(lines[i])) {
207
+ tableSeparatorIndex = i;
208
+ continue;
209
+ }
210
+ if (tableSeparatorIndex !== -1 && /^\s*\|/.test(lines[i])) {
211
+ tableDataIndex = i;
212
+ break;
213
+ }
214
+ }
215
+ if (tableDataIndex !== -1) {
216
+ lines[tableDataIndex] = row;
217
+ }
218
+ else if (tableSeparatorIndex !== -1) {
219
+ lines.splice(tableSeparatorIndex + 1, 0, row);
220
+ }
221
+ else {
222
+ lines.splice(headingIndex + 1, 0, "", "| Version | Date | Author | Description |", "|--------|------|--------|-------------|", row, "");
223
+ }
224
+ out = lines.join("\n");
225
+ return out;
226
+ }
227
+ function parseVersionToken(input) {
228
+ const match = input.match(/v?\s*(\d+)(?:\.(\d+))?/i);
229
+ if (!match)
230
+ return null;
231
+ const major = Number(match[1]);
232
+ const minor = Number(match[2] ?? "0");
233
+ if (!Number.isFinite(major) || !Number.isFinite(minor))
234
+ return null;
235
+ return { major, minor };
236
+ }
237
+ function extractDocumentControlVersion(body) {
238
+ const tableMatch = body.match(/\|\s*(v?\d+(?:\.\d+)?)\s*\|/i);
239
+ if (tableMatch?.[1])
240
+ return tableMatch[1].trim().startsWith("v") ? tableMatch[1].trim() : `v${tableMatch[1].trim()}`;
241
+ const looseMatch = body.match(/\bv?\d+\.\d+\b/i);
242
+ if (looseMatch?.[0])
243
+ return looseMatch[0].startsWith("v") ? looseMatch[0] : `v${looseMatch[0]}`;
244
+ return undefined;
245
+ }
246
+ async function resolveDocumentControlVersion(cwd, artifactType, revisionType) {
247
+ if (revisionType !== "fix")
248
+ return "v1.0";
249
+ const activePath = await (0, output_index_1.getActiveArtifactPath)(cwd, artifactType);
250
+ const fallbackPath = activePath ?? (await loadLatestArtifactPath(cwd, artifactType));
251
+ if (!fallbackPath || !(await (0, utils_1.fileExists)(fallbackPath))) {
252
+ return "v1.1";
253
+ }
254
+ try {
255
+ const previous = await loadArtifactDoc(fallbackPath);
256
+ const previousVersion = extractDocumentControlVersion(previous.body) ?? String(previous.frontmatter.version ?? "");
257
+ const parsed = parseVersionToken(previousVersion);
258
+ if (!parsed)
259
+ return "v1.1";
260
+ return `v${parsed.major}.${parsed.minor + 1}`;
261
+ }
262
+ catch {
263
+ return "v1.1";
264
+ }
265
+ }
266
+ function enforceAuthorInControlTables(body, author) {
267
+ const safeAuthor = normalizeAuthor(author);
268
+ if (!safeAuthor)
269
+ return body;
270
+ return body.replace(/(\|\s*v?[0-9.]+\s*\|\s*[^|]*\|\s*)([^|]*)(\|\s*[^|]*\|)/gi, (_match, left, _current, right) => `${left}${safeAuthor} ${right}`);
271
+ }
272
+ async function resolveUniqueOutputPath(targetDir, fileName) {
273
+ const parsed = node_path_1.default.parse(fileName);
274
+ let candidate = node_path_1.default.join(targetDir, fileName);
275
+ let index = 2;
276
+ while (await (0, utils_1.fileExists)(candidate)) {
277
+ candidate = node_path_1.default.join(targetDir, `${parsed.name}-${String(index).padStart(2, "0")}${parsed.ext}`);
278
+ index += 1;
279
+ }
280
+ return candidate;
281
+ }
282
+ function workflowFeatureTargets(normalized, coverage) {
283
+ const byId = new Map(normalized.contracts.core_features.map((item) => [item.id, item]));
284
+ const explicit = coverage.core_features
285
+ .map((id) => byId.get(id))
286
+ .filter((item) => Boolean(item));
287
+ if (explicit.length > 1)
288
+ return explicit;
289
+ if (normalized.contracts.core_features.length > 1)
290
+ return normalized.contracts.core_features.slice(0, 6);
291
+ if (explicit.length === 1)
292
+ return explicit;
293
+ return normalized.contracts.core_features.slice(0, 1);
294
+ }
295
+ function renderWorkflowMarkdownForFeature(markdown, feature, lang) {
296
+ const tr = lang.toLowerCase().startsWith("tr");
297
+ const noteHeading = tr ? "## Akis Odagi" : "## Flow Focus";
298
+ const noteLine = tr
299
+ ? `- [${feature.id}] Bu akis ${feature.text} ihtiyacina odaklanir.`
300
+ : `- [${feature.id}] This flow focuses on ${feature.text}.`;
301
+ if (markdown.includes(noteHeading))
302
+ return markdown;
303
+ return `${markdown.trim()}\n\n${noteHeading}\n${noteLine}`.trim();
304
+ }
305
+ async function resolvePrompt(cwd, artifactType, templateContent, requiredHeadings, companionTemplate, outputAuthor, agent) {
151
306
  const base = await promises_1.default.readFile((0, paths_1.promptPath)(cwd, artifactType), "utf8");
152
307
  const authority = `Template authority (STRICT):
153
308
  - Treat this template as the single output structure source.
@@ -187,12 +342,19 @@ Wireframe paired output contract (STRICT):
187
342
  - Generate companion HTML screens based on native wireframe template.
188
343
  - HTML must stay low-fidelity and structure-first.`
189
344
  : "";
345
+ const authorPolicy = outputAuthor && outputAuthor.trim().length > 0
346
+ ? `
347
+ Author policy (STRICT):
348
+ - Use this exact author name wherever author is required: ${outputAuthor.trim()}
349
+ - Do not invent random author names.`
350
+ : "";
190
351
  const withTemplate = `${base}
191
352
 
192
353
  ${authority}
193
354
  ${companionAuthority}
194
355
  ${workflowPairing}
195
- ${wireframePairing}`;
356
+ ${wireframePairing}
357
+ ${authorPolicy}`;
196
358
  if (!agent)
197
359
  return withTemplate;
198
360
  return `${withTemplate}
@@ -345,12 +507,59 @@ function splitWorkflowPair(raw) {
345
507
  }
346
508
  return { markdown, mermaid };
347
509
  }
510
+ async function writeWorkflowFlows(targetDir, baseName, normalized, coverage, lang, markdownBody, mermaidBody, mermaidTemplateContent) {
511
+ const targets = workflowFeatureTargets(normalized, coverage);
512
+ const fallbackFeature = normalized.contracts.core_features[0] ?? { id: "F1", text: "Primary flow" };
513
+ const flows = targets.length > 0 ? targets : [fallbackFeature];
514
+ const summaryBodies = [];
515
+ const renderedArtifacts = [];
516
+ let primaryMdPath = "";
517
+ for (const [index, flowFeature] of flows.entries()) {
518
+ const flowBase = flows.length === 1
519
+ ? baseName
520
+ : (index === 0
521
+ ? baseName
522
+ : `${baseName}-${index + 1}-${toSlug(extractTurkishTitle(flowFeature.text))}`);
523
+ const mdPath = node_path_1.default.join(targetDir, `${flowBase}.md`);
524
+ const mmdPath = node_path_1.default.join(targetDir, `${flowBase}.mmd`);
525
+ const featureCoverage = {
526
+ ...coverage,
527
+ core_features: [flowFeature.id]
528
+ };
529
+ const renderedMarkdown = renderWorkflowMarkdownForFeature(markdownBody, flowFeature, lang);
530
+ const renderedMermaid = (mermaidTemplateContent && mermaidTemplateContent.trim().length > 0)
531
+ ? renderWorkflowMermaidTemplate(mermaidTemplateContent, normalized, featureCoverage, lang).trim()
532
+ : (mermaidBody ?? "").trim();
533
+ if (!/(^|\n)\s*(flowchart|graph)\s+/i.test(renderedMermaid)) {
534
+ throw new errors_1.UserError("Workflow Mermaid output is invalid.");
535
+ }
536
+ enforceLanguage(renderedMarkdown, lang, "workflow");
537
+ enforceLanguage(renderedMermaid, lang, "workflow");
538
+ await promises_1.default.writeFile(mdPath, `${renderedMarkdown}\n`, "utf8");
539
+ await promises_1.default.writeFile(mmdPath, `${renderedMermaid}\n`, "utf8");
540
+ if (!primaryMdPath)
541
+ primaryMdPath = mdPath;
542
+ summaryBodies.push(renderedMarkdown);
543
+ renderedArtifacts.push({ mdPath, body: renderedMarkdown });
544
+ }
545
+ return {
546
+ primaryPath: primaryMdPath,
547
+ summaryBody: summaryBodies.join("\n\n"),
548
+ rendered: renderedArtifacts
549
+ };
550
+ }
348
551
  async function writeWireframeScreens(targetDir, baseName, normalized, coverage, lang, headings, htmlTemplateContent) {
349
552
  const tr = lang.toLowerCase().startsWith("tr");
350
- const screenContracts = normalized.contracts.core_features
553
+ const explicitScreens = normalized.contracts.core_features
351
554
  .filter((item) => coverage.core_features.includes(item.id))
352
555
  .slice(0, 6);
353
- const screens = screenContracts.length > 0 ? screenContracts : normalized.contracts.core_features.slice(0, 3);
556
+ const screens = explicitScreens.length > 1
557
+ ? explicitScreens
558
+ : (normalized.contracts.core_features.length > 1
559
+ ? normalized.contracts.core_features.slice(0, 6)
560
+ : (explicitScreens.length === 1
561
+ ? explicitScreens
562
+ : normalized.contracts.core_features.slice(0, 1)));
354
563
  const summaryBodies = [];
355
564
  let primaryMdPath = "";
356
565
  for (const [index, screen] of screens.entries()) {
@@ -486,9 +695,11 @@ async function writeWireframeScreens(targetDir, baseName, normalized, coverage,
486
695
  }
487
696
  async function generateArtifact(options) {
488
697
  const { cwd, artifactType, outPath, agent } = options;
698
+ const revisionType = options.revisionType ?? "default";
489
699
  const def = await (0, artifact_registry_1.getArtifactDefinition)(cwd, artifactType);
490
700
  const normalizedPath = options.normalizedBriefOverride ?? (0, paths_1.normalizedBriefPath)(cwd);
491
701
  await ensurePipelinePrereqs(cwd, normalizedPath);
702
+ const documentControlVersion = await resolveDocumentControlVersion(cwd, artifactType, revisionType);
492
703
  const settings = await (0, settings_1.readSettings)(cwd);
493
704
  const normalizedBriefRaw = await (0, utils_1.readJsonFile)(normalizedPath);
494
705
  const normalizedBrief = (0, normalized_brief_1.parseNormalizedBriefOrThrow)(normalizedBriefRaw);
@@ -510,7 +721,7 @@ async function generateArtifact(options) {
510
721
  const computedHeadings = templateHeadings.length > 0
511
722
  ? templateHeadings
512
723
  : (def.required_headings.length > 0 ? def.required_headings : (0, constants_1.defaultRequiredHeadings)(artifactType));
513
- const prompt = await resolvePrompt(cwd, artifactType, template?.content ?? "", computedHeadings, companionTemplate, agent);
724
+ const prompt = await resolvePrompt(cwd, artifactType, template?.content ?? "", computedHeadings, companionTemplate, settings.author, agent);
514
725
  const provider = (0, providers_1.createProvider)();
515
726
  const upstreamArtifacts = await buildUpstreamArtifacts(cwd, artifactType, def.upstream);
516
727
  const schemaHint = {
@@ -526,14 +737,15 @@ async function generateArtifact(options) {
526
737
  templatePath: template?.path ?? "",
527
738
  companionTemplateContent: companionTemplate?.content ?? "",
528
739
  companionTemplatePath: companionTemplate?.path ?? "",
529
- outputLanguage: settings.lang
740
+ outputLanguage: settings.lang,
741
+ outputAuthor: settings.author
530
742
  }, schemaHint);
531
- let generatedBody = generated.body.trim();
743
+ let generatedBody = enforceAuthorInControlTables(replaceAuthorPlaceholders(generated.body.trim(), settings.author), settings.author);
532
744
  let workflowMermaidBody = null;
533
745
  if (artifactType === "workflow") {
534
746
  const paired = splitWorkflowPair(generatedBody);
535
- generatedBody = paired.markdown;
536
- workflowMermaidBody = paired.mermaid;
747
+ generatedBody = enforceAuthorInControlTables(replaceAuthorPlaceholders(paired.markdown, settings.author), settings.author);
748
+ workflowMermaidBody = replaceAuthorPlaceholders(paired.mermaid, settings.author);
537
749
  }
538
750
  let contractCoverage = extractCoverageFromBody(generatedBody);
539
751
  if (artifactType === "workflow") {
@@ -552,8 +764,15 @@ async function generateArtifact(options) {
552
764
  };
553
765
  }
554
766
  }
767
+ generatedBody = applyDocumentControlDefaults(generatedBody, {
768
+ lang: settings.lang,
769
+ revisionType,
770
+ version: documentControlVersion,
771
+ author: settings.author
772
+ });
555
773
  if (artifactType === "workflow" && companionTemplate?.content) {
556
774
  workflowMermaidBody = renderWorkflowMermaidTemplate(companionTemplate.content, normalizedBrief, contractCoverage, settings.lang).trim();
775
+ workflowMermaidBody = replaceAuthorPlaceholders(workflowMermaidBody, settings.author);
557
776
  }
558
777
  enforceLanguage(generatedBody, settings.lang, artifactType);
559
778
  if (artifactType === "workflow" && workflowMermaidBody) {
@@ -574,9 +793,13 @@ async function generateArtifact(options) {
574
793
  status: constants_1.DEFAULT_STATUS,
575
794
  upstream_artifacts: upstreamArtifacts.map((item) => item.file),
576
795
  contract_coverage: contractCoverage,
577
- language: settings.lang
796
+ language: settings.lang,
797
+ ...(normalizeAuthor(settings.author) ? { author: normalizeAuthor(settings.author) } : {})
578
798
  };
579
799
  const mergedFrontmatter = { ...frontmatter, ...(generated.frontmatter ?? {}) };
800
+ if (normalizeAuthor(settings.author)) {
801
+ mergedFrontmatter.author = normalizeAuthor(settings.author);
802
+ }
580
803
  let doc = {
581
804
  frontmatter: mergedFrontmatter,
582
805
  body: generatedBody
@@ -588,29 +811,40 @@ async function generateArtifact(options) {
588
811
  throw new errors_1.UserError(`Artifact failed schema checks:\n${details}`);
589
812
  }
590
813
  const targetDir = (0, paths_1.outputDirPath)(cwd, artifactType, def.output_dir);
591
- const finalPath = outPath ? node_path_1.default.resolve(cwd, outPath) : node_path_1.default.join(targetDir, defaultFilename(artifactType));
814
+ const finalPath = outPath
815
+ ? node_path_1.default.resolve(cwd, outPath)
816
+ : await resolveUniqueOutputPath(targetDir, defaultFilename(artifactType));
592
817
  if (!(0, utils_1.isPathInside)(node_path_1.default.join(cwd, "product-docs"), finalPath)) {
593
818
  throw new errors_1.UserError("Artifact output must be inside `product-docs/`.");
594
819
  }
595
820
  await promises_1.default.mkdir(node_path_1.default.dirname(finalPath), { recursive: true });
596
821
  if (artifactType === "workflow") {
597
822
  const basePath = node_path_1.default.join(node_path_1.default.dirname(finalPath), node_path_1.default.parse(finalPath).name);
598
- const mdPath = `${basePath}.md`;
599
- const mmdPath = `${basePath}.mmd`;
600
- await promises_1.default.writeFile(mdPath, gray_matter_1.default.stringify(doc.body, doc.frontmatter), "utf8");
601
- await promises_1.default.writeFile(mmdPath, `${(workflowMermaidBody ?? "").trim()}\n`, "utf8");
602
- await writeSidecar(mdPath, doc);
603
- const derivedContext = {
604
- artifact_type: artifactType,
605
- artifact_file: mdPath,
606
- generated_at: new Date().toISOString(),
607
- contract_coverage: contractCoverage,
608
- ...deriveStructuredContext(artifactType, doc.body, schemaHint.requiredHeadings)
609
- };
823
+ 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);
610
824
  await promises_1.default.mkdir((0, paths_1.outputContextDirPath)(cwd), { recursive: true });
611
- await promises_1.default.writeFile(contextFilePath(cwd, mdPath), `${JSON.stringify(derivedContext, null, 2)}\n`, "utf8");
612
- await (0, output_index_1.setActiveArtifact)(cwd, artifactType, mdPath);
613
- return mdPath;
825
+ for (const rendered of workflow.rendered) {
826
+ const renderedDoc = {
827
+ frontmatter: doc.frontmatter,
828
+ body: rendered.body
829
+ };
830
+ await promises_1.default.writeFile(rendered.mdPath, gray_matter_1.default.stringify(renderedDoc.body, renderedDoc.frontmatter), "utf8");
831
+ await writeSidecar(rendered.mdPath, renderedDoc);
832
+ const renderedContext = {
833
+ artifact_type: artifactType,
834
+ artifact_file: rendered.mdPath,
835
+ generated_at: new Date().toISOString(),
836
+ contract_coverage: contractCoverage,
837
+ ...deriveStructuredContext(artifactType, renderedDoc.body, schemaHint.requiredHeadings)
838
+ };
839
+ await promises_1.default.writeFile(contextFilePath(cwd, rendered.mdPath), `${JSON.stringify(renderedContext, null, 2)}\n`, "utf8");
840
+ }
841
+ const primaryRendered = workflow.rendered.find((item) => item.mdPath === workflow.primaryPath) ?? workflow.rendered[0];
842
+ doc = {
843
+ frontmatter: doc.frontmatter,
844
+ body: primaryRendered?.body ?? doc.body
845
+ };
846
+ await (0, output_index_1.setActiveArtifact)(cwd, artifactType, workflow.primaryPath);
847
+ return workflow.primaryPath;
614
848
  }
615
849
  else if (artifactType === "wireframe") {
616
850
  const base = node_path_1.default.parse(finalPath).name;
@@ -0,0 +1,4 @@
1
+ export declare const AI_ALIASES: Record<string, "codex" | "gemini-cli" | "claude-cli">;
2
+ export type SupportedAi = "codex" | "gemini-cli" | "claude-cli";
3
+ export declare function resolveAi(ai?: string): SupportedAi | undefined;
4
+ export declare function installAgentCommands(projectRoot: string, ai: SupportedAi): Promise<string[]>;