@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,21 @@
1
+ import { type NormalizedBrief } from "./normalized-brief";
2
+ import type { ArtifactDoc, ArtifactType, ValidationIssue } from "./types";
3
+ type LoadedArtifact = {
4
+ type: ArtifactType;
5
+ file: string;
6
+ doc: ArtifactDoc;
7
+ };
8
+ export type TraceEntry = {
9
+ contractId: string;
10
+ contractText: string;
11
+ category: "goals" | "core_features" | "constraints";
12
+ references: Array<{
13
+ artifactType: ArtifactType;
14
+ file: string;
15
+ line: string;
16
+ }>;
17
+ };
18
+ export type TraceMap = Map<string, TraceEntry>;
19
+ export declare function buildTraceMap(normalizedBrief: NormalizedBrief, loadedArtifacts: LoadedArtifact[]): TraceMap;
20
+ export declare function checkRequirementCompleteness(traceMap: TraceMap, normalizedBrief: NormalizedBrief, presentArtifactTypes: ArtifactType[]): ValidationIssue[];
21
+ export {};
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildTraceMap = buildTraceMap;
4
+ exports.checkRequirementCompleteness = checkRequirementCompleteness;
5
+ const markdown_1 = require("./markdown");
6
+ function buildTraceMap(normalizedBrief, loadedArtifacts) {
7
+ const traceMap = new Map();
8
+ function registerContracts(items, category) {
9
+ for (const item of items) {
10
+ traceMap.set(item.id, {
11
+ contractId: item.id,
12
+ contractText: item.text,
13
+ category,
14
+ references: []
15
+ });
16
+ }
17
+ }
18
+ registerContracts(normalizedBrief.contracts.goals, "goals");
19
+ registerContracts(normalizedBrief.contracts.core_features, "core_features");
20
+ registerContracts(normalizedBrief.contracts.constraints, "constraints");
21
+ for (const artifact of loadedArtifacts) {
22
+ const tagged = (0, markdown_1.taggedLinesByContract)(artifact.doc.body);
23
+ for (const { contractId, line } of tagged) {
24
+ const entry = traceMap.get(contractId);
25
+ if (entry) {
26
+ entry.references.push({
27
+ artifactType: artifact.type,
28
+ file: artifact.file,
29
+ line
30
+ });
31
+ }
32
+ }
33
+ }
34
+ return traceMap;
35
+ }
36
+ function checkRequirementCompleteness(traceMap, normalizedBrief, presentArtifactTypes) {
37
+ const issues = [];
38
+ if (presentArtifactTypes.length === 0)
39
+ return issues;
40
+ for (const [contractId, entry] of traceMap) {
41
+ if (entry.references.length === 0) {
42
+ issues.push({
43
+ level: "warning",
44
+ code: "untraced_requirement",
45
+ check: "tracing",
46
+ message: `Contract ${contractId} ("${truncate(entry.contractText, 60)}") is not referenced in any generated artifact.`,
47
+ suggestion: `Ensure [${contractId}] tag appears in at least one artifact body.`
48
+ });
49
+ }
50
+ }
51
+ const hasPrd = presentArtifactTypes.includes("prd");
52
+ if (hasPrd) {
53
+ for (const [contractId, entry] of traceMap) {
54
+ if (entry.category === "goals") {
55
+ const inPrd = entry.references.some((r) => r.artifactType === "prd");
56
+ if (!inPrd && entry.references.length > 0) {
57
+ issues.push({
58
+ level: "warning",
59
+ code: "goal_missing_in_prd",
60
+ check: "tracing",
61
+ message: `Goal ${contractId} appears in downstream artifacts but is not traced in the PRD.`,
62
+ suggestion: `Add [${contractId}] tag to the PRD to maintain traceability.`
63
+ });
64
+ }
65
+ }
66
+ }
67
+ }
68
+ return issues;
69
+ }
70
+ function truncate(text, maxLength) {
71
+ if (text.length <= maxLength)
72
+ return text;
73
+ return `${text.slice(0, maxLength - 3)}...`;
74
+ }
@@ -0,0 +1,35 @@
1
+ export declare const CORE_ARTIFACT_TYPES: readonly ["prd", "workflow", "wireframe", "stories", "techspec"];
2
+ export declare const ARTIFACT_TYPES: readonly ["prd", "workflow", "wireframe", "stories", "techspec"];
3
+ export type CoreArtifactType = (typeof CORE_ARTIFACT_TYPES)[number];
4
+ export type ArtifactType = CoreArtifactType | string;
5
+ export type ArtifactDoc = {
6
+ frontmatter: Record<string, unknown>;
7
+ body: string;
8
+ };
9
+ export type ContractCoverage = {
10
+ goals: string[];
11
+ core_features: string[];
12
+ constraints: string[];
13
+ };
14
+ export type ValidationIssue = {
15
+ level: "error" | "warning";
16
+ code: string;
17
+ check?: "schema" | "tag_coverage" | "semantic_consistency" | "contract_relevance" | "terminology" | "tracing" | "cross_reference";
18
+ artifactType?: ArtifactType;
19
+ file?: string;
20
+ field?: string;
21
+ message: string;
22
+ suggestion?: string;
23
+ };
24
+ export type GenerateResult = {
25
+ body: string;
26
+ frontmatter?: Record<string, unknown>;
27
+ };
28
+ export type ProviderSchemaHint = {
29
+ artifactType: ArtifactType | "normalize" | "semantic_consistency" | "contract_relevance";
30
+ requiredHeadings: string[];
31
+ requiredContracts: Array<keyof ContractCoverage>;
32
+ };
33
+ export type LLMProvider = {
34
+ generate: (prompt: string, inputContext: Record<string, unknown>, schemaHint: ProviderSchemaHint) => Promise<GenerateResult>;
35
+ };
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ARTIFACT_TYPES = exports.CORE_ARTIFACT_TYPES = void 0;
4
+ exports.CORE_ARTIFACT_TYPES = ["prd", "workflow", "wireframe", "stories", "techspec"];
5
+ exports.ARTIFACT_TYPES = exports.CORE_ARTIFACT_TYPES;
@@ -0,0 +1,7 @@
1
+ export declare function ensureDir(dirPath: string): Promise<void>;
2
+ export declare function fileExists(filePath: string): Promise<boolean>;
3
+ export declare function readJsonFile<T>(filePath: string): Promise<T>;
4
+ export declare function timestampSlug(date?: Date): string;
5
+ export declare function artifactFileStamp(date?: Date): string;
6
+ export declare function listFilesSortedByMtime(dirPath: string): Promise<string[]>;
7
+ export declare function isPathInside(parentDir: string, candidatePath: string): boolean;
@@ -0,0 +1,66 @@
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.ensureDir = ensureDir;
7
+ exports.fileExists = fileExists;
8
+ exports.readJsonFile = readJsonFile;
9
+ exports.timestampSlug = timestampSlug;
10
+ exports.artifactFileStamp = artifactFileStamp;
11
+ exports.listFilesSortedByMtime = listFilesSortedByMtime;
12
+ exports.isPathInside = isPathInside;
13
+ const promises_1 = __importDefault(require("node:fs/promises"));
14
+ const node_path_1 = __importDefault(require("node:path"));
15
+ async function ensureDir(dirPath) {
16
+ await promises_1.default.mkdir(dirPath, { recursive: true });
17
+ }
18
+ async function fileExists(filePath) {
19
+ try {
20
+ await promises_1.default.access(filePath);
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ async function readJsonFile(filePath) {
28
+ const raw = await promises_1.default.readFile(filePath, "utf8");
29
+ return JSON.parse(raw);
30
+ }
31
+ function timestampSlug(date = new Date()) {
32
+ return date.toISOString().replace(/[:.]/g, "-");
33
+ }
34
+ function pad2(value) {
35
+ return String(value).padStart(2, "0");
36
+ }
37
+ function artifactFileStamp(date = new Date()) {
38
+ const year = date.getFullYear();
39
+ const month = pad2(date.getMonth() + 1);
40
+ const day = pad2(date.getDate());
41
+ const hour = pad2(date.getHours());
42
+ const minute = pad2(date.getMinutes());
43
+ const second = pad2(date.getSeconds());
44
+ return `${year}${month}${day}-${hour}${minute}${second}`;
45
+ }
46
+ async function listFilesSortedByMtime(dirPath) {
47
+ const exists = await fileExists(dirPath);
48
+ if (!exists)
49
+ return [];
50
+ const entries = await promises_1.default.readdir(dirPath);
51
+ const withStats = await Promise.all(entries.map(async (name) => {
52
+ const fullPath = node_path_1.default.join(dirPath, name);
53
+ const stat = await promises_1.default.stat(fullPath);
54
+ return { fullPath, mtimeMs: stat.mtimeMs, isFile: stat.isFile() };
55
+ }));
56
+ return withStats
57
+ .filter((entry) => entry.isFile)
58
+ .sort((a, b) => b.mtimeMs - a.mtimeMs)
59
+ .map((entry) => entry.fullPath);
60
+ }
61
+ function isPathInside(parentDir, candidatePath) {
62
+ const parent = node_path_1.default.resolve(parentDir);
63
+ const child = node_path_1.default.resolve(candidatePath);
64
+ const relative = node_path_1.default.relative(parent, child);
65
+ return relative === "" || (!relative.startsWith("..") && !node_path_1.default.isAbsolute(relative));
66
+ }
@@ -0,0 +1,10 @@
1
+ import type { ValidationIssue } from "./types";
2
+ export type ValidateResult = {
3
+ pass: boolean;
4
+ reportPath: string;
5
+ issues: ValidationIssue[];
6
+ };
7
+ export declare function runValidate(cwd: string, options: {
8
+ strict?: boolean;
9
+ report?: string;
10
+ }): Promise<ValidateResult>;
@@ -0,0 +1,226 @@
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.runValidate = runValidate;
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 artifact_registry_1 = require("./artifact-registry");
11
+ const consistency_1 = require("./consistency");
12
+ const errors_1 = require("./errors");
13
+ const output_index_1 = require("./output-index");
14
+ const paths_1 = require("./paths");
15
+ const template_resolver_1 = require("./template-resolver");
16
+ const utils_1 = require("./utils");
17
+ const validator_1 = require("./validator");
18
+ function sidecarPath(filePath) {
19
+ const parsed = node_path_1.default.parse(filePath);
20
+ return node_path_1.default.join(parsed.dir, `${parsed.name}.artifact.json`);
21
+ }
22
+ async function loadArtifactDoc(filePath) {
23
+ const sidecar = sidecarPath(filePath);
24
+ if (await (0, utils_1.fileExists)(sidecar)) {
25
+ const payload = await (0, utils_1.readJsonFile)(sidecar);
26
+ return {
27
+ frontmatter: payload.frontmatter ?? {},
28
+ body: typeof payload.body === "string" ? payload.body : ""
29
+ };
30
+ }
31
+ const raw = await promises_1.default.readFile(filePath, "utf8");
32
+ const parsed = (0, gray_matter_1.default)(raw);
33
+ return {
34
+ frontmatter: parsed.data,
35
+ body: parsed.content
36
+ };
37
+ }
38
+ async function loadLatestArtifacts(cwd) {
39
+ const defs = await (0, artifact_registry_1.listArtifactDefinitions)(cwd);
40
+ const loaded = [];
41
+ for (const def of defs) {
42
+ const type = def.name;
43
+ const active = await (0, output_index_1.getActiveArtifactPath)(cwd, type);
44
+ const fallback = async () => {
45
+ const files = await (0, utils_1.listFilesSortedByMtime)((0, paths_1.outputDirPath)(cwd, type, def.output_dir));
46
+ return files[0];
47
+ };
48
+ const latest = active ?? (await fallback());
49
+ if (!latest)
50
+ continue;
51
+ const parsed = await loadArtifactDoc(latest);
52
+ loaded.push({
53
+ type,
54
+ file: latest,
55
+ doc: parsed
56
+ });
57
+ }
58
+ return loaded;
59
+ }
60
+ async function listHtmlFiles(dir) {
61
+ if (!(await (0, utils_1.fileExists)(dir)))
62
+ return [];
63
+ const entries = await promises_1.default.readdir(dir, { withFileTypes: true });
64
+ return entries
65
+ .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".html"))
66
+ .map((entry) => node_path_1.default.join(dir, entry.name));
67
+ }
68
+ function formatIssue(issue) {
69
+ const location = issue.file ? ` (${issue.file})` : "";
70
+ const field = issue.field ? ` [${issue.field}]` : "";
71
+ const check = issue.check ? ` [${issue.check}]` : "";
72
+ const fix = issue.suggestion ? `\n Suggestion: ${issue.suggestion}` : "";
73
+ return `- [${issue.level.toUpperCase()}] ${issue.code}${check}${field}: ${issue.message}${location}${fix}`;
74
+ }
75
+ async function writeReport(targetPath, issues) {
76
+ const status = issues.some((issue) => issue.level === "error") ? "FAIL" : "PASS";
77
+ const schemaIssues = issues.filter((issue) => issue.check === "schema");
78
+ const coverageIssues = issues.filter((issue) => issue.check === "tag_coverage");
79
+ const relevanceIssues = issues.filter((issue) => issue.check === "contract_relevance");
80
+ const semanticIssues = issues.filter((issue) => issue.check === "semantic_consistency");
81
+ const gate = (set) => (set.some((issue) => issue.level === "error") ? "FAIL" : "PASS");
82
+ const content = [
83
+ "# Prodo Validation Report",
84
+ "",
85
+ `Status: **${status}**`,
86
+ `Generated at: ${new Date().toISOString()}`,
87
+ "",
88
+ "## Gate Results",
89
+ `- Schema pass: ${gate(schemaIssues)}`,
90
+ `- Tag coverage pass: ${gate(coverageIssues)}`,
91
+ `- Contract relevance pass: ${gate(relevanceIssues)}`,
92
+ `- Semantic consistency pass: ${gate(semanticIssues)}`,
93
+ "",
94
+ "## Findings",
95
+ issues.length === 0 ? "- No issues found." : issues.map(formatIssue).join("\n"),
96
+ ""
97
+ ].join("\n");
98
+ await (0, utils_1.ensureDir)(node_path_1.default.dirname(targetPath));
99
+ await promises_1.default.writeFile(targetPath, content, "utf8");
100
+ }
101
+ async function runValidate(cwd, options) {
102
+ const normalizedPath = (0, paths_1.normalizedBriefPath)(cwd);
103
+ if (!(await (0, utils_1.fileExists)(normalizedPath))) {
104
+ throw new errors_1.UserError("Missing `.prodo/briefs/normalized-brief.json`. Run `prodo-init` and create it first.");
105
+ }
106
+ const normalizedBrief = await (0, utils_1.readJsonFile)(normalizedPath);
107
+ const loaded = await loadLatestArtifacts(cwd);
108
+ const issues = [];
109
+ for (const artifact of loaded) {
110
+ const template = await (0, template_resolver_1.resolveTemplate)({
111
+ cwd,
112
+ artifactType: artifact.type
113
+ });
114
+ const headings = template ? (0, template_resolver_1.extractRequiredHeadingsFromTemplate)(template.content) : [];
115
+ const schemaCheck = await (0, validator_1.validateSchema)(cwd, artifact.type, artifact.doc, headings);
116
+ issues.push(...schemaCheck.issues.map((issue) => ({ ...issue, file: artifact.file })));
117
+ if (artifact.type === "workflow") {
118
+ const ext = node_path_1.default.extname(artifact.file).toLowerCase();
119
+ if (ext !== ".md") {
120
+ issues.push({
121
+ level: "error",
122
+ code: "workflow_markdown_missing",
123
+ check: "schema",
124
+ artifactType: artifact.type,
125
+ file: artifact.file,
126
+ message: "Workflow explanation artifact must be Markdown (.md).",
127
+ suggestion: "Regenerate workflow so explanation is written to .md."
128
+ });
129
+ }
130
+ const mmdPath = node_path_1.default.join(node_path_1.default.dirname(artifact.file), `${node_path_1.default.parse(artifact.file).name}.mmd`);
131
+ if (!(await (0, utils_1.fileExists)(mmdPath))) {
132
+ issues.push({
133
+ level: "error",
134
+ code: "workflow_mermaid_missing",
135
+ check: "schema",
136
+ artifactType: artifact.type,
137
+ file: artifact.file,
138
+ message: "Workflow Mermaid companion file (.mmd) is missing.",
139
+ suggestion: "Regenerate workflow so markdown and .mmd are produced as a pair."
140
+ });
141
+ }
142
+ else {
143
+ const mmdRaw = await promises_1.default.readFile(mmdPath, "utf8");
144
+ const mermaidLike = /(^|\n)\s*flowchart\s+/i.test(mmdRaw) || /(^|\n)\s*graph\s+/i.test(mmdRaw);
145
+ if (!mermaidLike) {
146
+ issues.push({
147
+ level: "error",
148
+ code: "workflow_mermaid_invalid",
149
+ check: "schema",
150
+ artifactType: artifact.type,
151
+ file: mmdPath,
152
+ message: "Workflow Mermaid file is invalid or prose-only.",
153
+ suggestion: "Ensure .mmd file contains valid Mermaid diagram syntax."
154
+ });
155
+ }
156
+ }
157
+ }
158
+ if (artifact.type === "wireframe") {
159
+ const ext = node_path_1.default.extname(artifact.file).toLowerCase();
160
+ if (ext !== ".md") {
161
+ issues.push({
162
+ level: "error",
163
+ code: "wireframe_markdown_missing",
164
+ check: "schema",
165
+ artifactType: artifact.type,
166
+ file: artifact.file,
167
+ message: "Wireframe explanation artifact must be Markdown (.md).",
168
+ suggestion: "Regenerate wireframe so explanation is written to .md."
169
+ });
170
+ }
171
+ const htmlPath = node_path_1.default.join(node_path_1.default.dirname(artifact.file), `${node_path_1.default.parse(artifact.file).name}.html`);
172
+ if (!(await (0, utils_1.fileExists)(htmlPath))) {
173
+ issues.push({
174
+ level: "error",
175
+ code: "wireframe_html_missing",
176
+ check: "schema",
177
+ artifactType: artifact.type,
178
+ file: artifact.file,
179
+ message: "Wireframe HTML companion file is missing.",
180
+ suggestion: "Regenerate wireframe so markdown and .html are produced as a pair."
181
+ });
182
+ }
183
+ else {
184
+ const htmlRaw = await promises_1.default.readFile(htmlPath, "utf8");
185
+ const htmlLooksValid = /<!doctype html>/i.test(htmlRaw) || /<html[\s>]/i.test(htmlRaw);
186
+ if (!htmlLooksValid) {
187
+ issues.push({
188
+ level: "error",
189
+ code: "wireframe_html_invalid",
190
+ check: "schema",
191
+ artifactType: artifact.type,
192
+ file: htmlPath,
193
+ message: "Wireframe output is not valid HTML content.",
194
+ suggestion: "Ensure wireframe companion HTML contains a valid document structure."
195
+ });
196
+ }
197
+ }
198
+ const htmlFiles = await listHtmlFiles(node_path_1.default.dirname(artifact.file));
199
+ if (htmlFiles.length < 1) {
200
+ issues.push({
201
+ level: "error",
202
+ code: "wireframe_screens_missing",
203
+ check: "schema",
204
+ artifactType: artifact.type,
205
+ file: artifact.file,
206
+ message: "Wireframe must include at least one HTML screen artifact.",
207
+ suggestion: "Regenerate wireframe to create paired .md and .html screen files."
208
+ });
209
+ }
210
+ }
211
+ }
212
+ issues.push(...(await (0, consistency_1.checkConsistency)(cwd, loaded, normalizedBrief)));
213
+ if (options.strict) {
214
+ for (const issue of issues) {
215
+ if (issue.level === "warning")
216
+ issue.level = "error";
217
+ }
218
+ }
219
+ const finalReportPath = options.report ? node_path_1.default.resolve(cwd, options.report) : (0, paths_1.reportPath)(cwd);
220
+ if (!(0, utils_1.isPathInside)(node_path_1.default.join(cwd, "product-docs"), finalReportPath)) {
221
+ throw new errors_1.UserError("Validation report must be inside `product-docs/`.");
222
+ }
223
+ await writeReport(finalReportPath, issues);
224
+ const pass = !issues.some((issue) => issue.level === "error");
225
+ return { pass, reportPath: finalReportPath, issues };
226
+ }
@@ -0,0 +1,5 @@
1
+ import type { ArtifactDoc, ArtifactType, ValidationIssue } from "./types";
2
+ export declare function validateSchema(cwd: string, artifactType: ArtifactType, doc: ArtifactDoc, requiredHeadingsOverride?: string[]): Promise<{
3
+ issues: ValidationIssue[];
4
+ requiredHeadings: string[];
5
+ }>;
@@ -0,0 +1,76 @@
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.validateSchema = validateSchema;
7
+ const promises_1 = __importDefault(require("node:fs/promises"));
8
+ const _2020_1 = __importDefault(require("ajv/dist/2020"));
9
+ const ajv_formats_1 = __importDefault(require("ajv-formats"));
10
+ const js_yaml_1 = __importDefault(require("js-yaml"));
11
+ const markdown_1 = require("./markdown");
12
+ const paths_1 = require("./paths");
13
+ const ajv = new _2020_1.default({ allErrors: true, strict: false });
14
+ (0, ajv_formats_1.default)(ajv);
15
+ async function resolveSchema(cwd, artifactType) {
16
+ const raw = await promises_1.default.readFile((0, paths_1.schemaPath)(cwd, artifactType), "utf8");
17
+ return js_yaml_1.default.load(raw);
18
+ }
19
+ function requiredHeadingsFromSchema(schema) {
20
+ if (!Array.isArray(schema.x_required_headings))
21
+ return [];
22
+ return schema.x_required_headings.filter((item) => typeof item === "string");
23
+ }
24
+ async function validateSchema(cwd, artifactType, doc, requiredHeadingsOverride) {
25
+ const schema = await resolveSchema(cwd, artifactType);
26
+ const requiredHeadings = requiredHeadingsOverride && requiredHeadingsOverride.length > 0
27
+ ? requiredHeadingsOverride
28
+ : requiredHeadingsFromSchema(schema);
29
+ const workingSchema = { ...schema };
30
+ delete workingSchema.x_required_headings;
31
+ const validate = ajv.compile(workingSchema);
32
+ const valid = validate(doc);
33
+ const issues = [];
34
+ if (!valid && validate.errors) {
35
+ for (const err of validate.errors) {
36
+ issues.push({
37
+ level: "error",
38
+ code: "schema_validation_failed",
39
+ check: "schema",
40
+ artifactType,
41
+ field: err.instancePath || err.schemaPath,
42
+ message: `Schema validation error: ${err.message ?? "unknown error"}`,
43
+ suggestion: "Adjust the generated content to satisfy schema requirements."
44
+ });
45
+ }
46
+ }
47
+ const sections = (0, markdown_1.sectionTextMap)(doc.body);
48
+ for (const heading of requiredHeadings) {
49
+ if (!doc.body.includes(heading)) {
50
+ issues.push({
51
+ level: "error",
52
+ code: "missing_required_heading",
53
+ check: "schema",
54
+ artifactType,
55
+ field: heading,
56
+ message: `Required section missing: ${heading}`,
57
+ suggestion: "Regenerate or manually edit the artifact to include all required headings."
58
+ });
59
+ continue;
60
+ }
61
+ const content = sections.get(heading) ?? "";
62
+ const isPlaceholder = /(tbd|to be defined|i don't know|unknown|n\/a)/i.test(content);
63
+ if (content.trim().length < 20 || isPlaceholder) {
64
+ issues.push({
65
+ level: "error",
66
+ code: "weak_required_heading_content",
67
+ check: "schema",
68
+ artifactType,
69
+ field: heading,
70
+ message: `Section has weak or placeholder content: ${heading}`,
71
+ suggestion: "Replace placeholders with concrete, actionable details."
72
+ });
73
+ }
74
+ }
75
+ return { issues, requiredHeadings };
76
+ }
@@ -0,0 +1 @@
1
+ export declare function readCliVersion(cwd: string): Promise<string>;
@@ -0,0 +1,30 @@
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.readCliVersion = readCliVersion;
7
+ const promises_1 = __importDefault(require("node:fs/promises"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const utils_1 = require("./utils");
10
+ async function readCliVersion(cwd) {
11
+ const candidates = [
12
+ node_path_1.default.join(cwd, "package.json"),
13
+ node_path_1.default.resolve(__dirname, "..", "..", "package.json")
14
+ ];
15
+ for (const candidate of candidates) {
16
+ if (!(await (0, utils_1.fileExists)(candidate)))
17
+ continue;
18
+ try {
19
+ const raw = await promises_1.default.readFile(candidate, "utf8");
20
+ const parsed = JSON.parse(raw);
21
+ if (typeof parsed.version === "string" && parsed.version.trim().length > 0) {
22
+ return parsed.version.trim();
23
+ }
24
+ }
25
+ catch {
26
+ // ignore and continue
27
+ }
28
+ }
29
+ return "0.0.0";
30
+ }
@@ -0,0 +1,7 @@
1
+ export type WorkflowCommand = {
2
+ name: string;
3
+ cliSubcommand: string;
4
+ description: string;
5
+ };
6
+ export declare const WORKFLOW_COMMANDS: WorkflowCommand[];
7
+ export declare function buildWorkflowCommands(artifactTypes: string[]): WorkflowCommand[];
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WORKFLOW_COMMANDS = void 0;
4
+ exports.buildWorkflowCommands = buildWorkflowCommands;
5
+ const BASE_WORKFLOW_COMMANDS = [
6
+ { name: "prodo-normalize", cliSubcommand: "normalize", description: "Normalize start brief into normalized brief JSON." },
7
+ { name: "prodo-prd", cliSubcommand: "prd", description: "Generate PRD artifact from normalized brief." },
8
+ { name: "prodo-workflow", cliSubcommand: "workflow", description: "Generate workflow artifact." },
9
+ { name: "prodo-wireframe", cliSubcommand: "wireframe", description: "Generate wireframe artifact." },
10
+ { name: "prodo-stories", cliSubcommand: "stories", description: "Generate stories artifact." },
11
+ { name: "prodo-techspec", cliSubcommand: "techspec", description: "Generate technical specification artifact." },
12
+ { name: "prodo-validate", cliSubcommand: "validate", description: "Run schema and cross-artifact consistency validation." },
13
+ { name: "prodo-fix", cliSubcommand: "fix", description: "Auto-fix artifacts based on validation report and brief." }
14
+ ];
15
+ exports.WORKFLOW_COMMANDS = BASE_WORKFLOW_COMMANDS;
16
+ function buildWorkflowCommands(artifactTypes) {
17
+ const commandByName = new Map(BASE_WORKFLOW_COMMANDS.map((item) => [item.name, item]));
18
+ for (const type of artifactTypes) {
19
+ const name = `prodo-${type}`;
20
+ if (commandByName.has(name))
21
+ continue;
22
+ commandByName.set(name, {
23
+ name,
24
+ cliSubcommand: type,
25
+ description: `Generate ${type} artifact from normalized brief.`
26
+ });
27
+ }
28
+ return Array.from(commandByName.values());
29
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "user_action": "User action",
3
+ "main_flow": "Main Flow",
4
+ "user": "User",
5
+ "success": "Success",
6
+ "error": "Error",
7
+ "flow_focus": "Flow Focus",
8
+ "initial_version": "Initial version",
9
+ "fix_revision": "Post-validation fix revision",
10
+ "primary_user": "Primary user",
11
+ "back": "Back",
12
+ "next": "Next",
13
+ "save": "Save",
14
+ "content": "Content",
15
+ "primary_info_area": "Primary information area",
16
+ "status_indicator": "Status indicator",
17
+ "form": "Form",
18
+ "field": "Field",
19
+ "description": "Description",
20
+ "contract": "Contract",
21
+ "actor": "Actor",
22
+ "detailed_input_area": "Detailed Input Area",
23
+ "upload_area": "Upload Area",
24
+ "low_fidelity_wireframe": "Low-fidelity wireframe.",
25
+ "confirmation_text": "Confirmation text",
26
+ "header_and_navigation": "Header and navigation",
27
+ "content_section": "Content section",
28
+ "form_section": "Form section",
29
+ "text_input": "Text input",
30
+ "to_be_refined": "To be refined.",
31
+ "note": "Note",
32
+ "requirement_item": "Requirement item",
33
+ "contract_coverage": "Contract coverage",
34
+ "screen": "Screen",
35
+ "for_artifact": "for",
36
+ "fix_proposal": "Fix Proposal",
37
+ "fix_complete": "Fix complete — validation passed.",
38
+ "fix_still_failing": "Fix applied but validation still failing.",
39
+ "fix_cancelled": "Fix cancelled.",
40
+ "no_issues": "No blocking issues found. Nothing to fix.",
41
+ "issues_found": "Issues found",
42
+ "artifacts_to_regenerate": "Artifacts to regenerate",
43
+ "general": "General",
44
+ "suggestion_prefix": "→"
45
+ }
@@ -0,0 +1,5 @@
1
+ type TranslationMap = Record<string, string>;
2
+ export declare function t(key: string, lang?: string): string;
3
+ export declare function loadTranslations(lang: string): TranslationMap;
4
+ export declare function availableLanguages(): string[];
5
+ export {};