@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.
- package/README.md +201 -97
- package/bin/prodo.cjs +6 -6
- package/dist/agents/agent-registry.d.ts +13 -0
- package/dist/agents/agent-registry.js +79 -0
- package/dist/agents/anthropic/index.d.ts +9 -0
- package/dist/agents/anthropic/index.js +55 -0
- package/dist/agents/base.d.ts +25 -0
- package/dist/agents/base.js +71 -0
- package/dist/agents/google/index.d.ts +9 -0
- package/dist/agents/google/index.js +53 -0
- package/dist/agents/mock/index.d.ts +11 -0
- package/dist/agents/mock/index.js +26 -0
- package/dist/agents/openai/index.d.ts +9 -0
- package/dist/agents/openai/index.js +57 -0
- package/dist/agents/system-prompts.d.ts +3 -0
- package/dist/agents/system-prompts.js +32 -0
- package/dist/agents.js +4 -2
- package/dist/artifacts.d.ts +1 -0
- package/dist/artifacts.js +265 -31
- package/dist/cli/agent-command-installer.d.ts +4 -0
- package/dist/cli/agent-command-installer.js +148 -0
- package/dist/cli/agent-ids.d.ts +15 -0
- package/dist/cli/agent-ids.js +49 -0
- package/dist/cli/doctor.d.ts +1 -0
- package/dist/cli/doctor.js +144 -0
- package/dist/cli/fix-tui.d.ts +4 -0
- package/dist/cli/fix-tui.js +79 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +465 -0
- package/dist/cli/init-tui.d.ts +23 -0
- package/dist/cli/init-tui.js +176 -0
- package/dist/cli/init.d.ts +11 -0
- package/dist/cli/init.js +334 -0
- package/dist/cli/normalize-interactive.d.ts +8 -0
- package/dist/cli/normalize-interactive.js +167 -0
- package/dist/cli/preset-loader.d.ts +4 -0
- package/dist/cli/preset-loader.js +210 -0
- package/dist/cli.js +80 -3
- package/dist/core/artifact-registry.d.ts +11 -0
- package/dist/core/artifact-registry.js +49 -0
- package/dist/core/artifacts.d.ts +10 -0
- package/dist/core/artifacts.js +892 -0
- package/dist/core/clean.d.ts +10 -0
- package/dist/core/clean.js +74 -0
- package/dist/core/consistency.d.ts +8 -0
- package/dist/core/consistency.js +328 -0
- package/dist/core/constants.d.ts +7 -0
- package/dist/core/constants.js +64 -0
- package/dist/core/errors.d.ts +3 -0
- package/dist/core/errors.js +10 -0
- package/dist/core/fix.d.ts +31 -0
- package/dist/core/fix.js +188 -0
- package/dist/core/hook-executor.d.ts +1 -0
- package/dist/core/hook-executor.js +175 -0
- package/dist/core/markdown.d.ts +16 -0
- package/dist/core/markdown.js +81 -0
- package/dist/core/normalize.d.ts +8 -0
- package/dist/core/normalize.js +125 -0
- package/dist/core/normalized-brief.d.ts +48 -0
- package/dist/core/normalized-brief.js +182 -0
- package/dist/core/output-index.d.ts +13 -0
- package/dist/core/output-index.js +55 -0
- package/dist/core/paths.d.ts +17 -0
- package/dist/core/paths.js +80 -0
- package/dist/core/project-config.d.ts +14 -0
- package/dist/core/project-config.js +69 -0
- package/dist/core/registry.d.ts +13 -0
- package/dist/core/registry.js +115 -0
- package/dist/core/settings.d.ts +7 -0
- package/dist/core/settings.js +35 -0
- package/dist/core/template-engine.d.ts +3 -0
- package/dist/core/template-engine.js +43 -0
- package/dist/core/template-resolver.d.ts +15 -0
- package/dist/core/template-resolver.js +46 -0
- package/dist/core/templates.d.ts +33 -0
- package/dist/core/templates.js +440 -0
- package/dist/core/terminology.d.ts +21 -0
- package/dist/core/terminology.js +143 -0
- package/dist/core/tracing.d.ts +21 -0
- package/dist/core/tracing.js +74 -0
- package/dist/core/types.d.ts +35 -0
- package/dist/core/types.js +5 -0
- package/dist/core/utils.d.ts +7 -0
- package/dist/core/utils.js +66 -0
- package/dist/core/validate.d.ts +10 -0
- package/dist/core/validate.js +226 -0
- package/dist/core/validator.d.ts +5 -0
- package/dist/core/validator.js +76 -0
- package/dist/core/version.d.ts +1 -0
- package/dist/core/version.js +30 -0
- package/dist/core/workflow-commands.d.ts +7 -0
- package/dist/core/workflow-commands.js +29 -0
- package/dist/i18n/en.json +45 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.js +63 -0
- package/dist/i18n/tr.json +45 -0
- package/dist/init-tui.d.ts +3 -0
- package/dist/init-tui.js +28 -1
- package/dist/init.d.ts +1 -0
- package/dist/init.js +9 -3
- package/dist/normalize.js +55 -7
- package/dist/providers/index.d.ts +2 -1
- package/dist/providers/index.js +20 -6
- package/dist/providers/mock-provider.d.ts +1 -1
- package/dist/providers/mock-provider.js +7 -6
- package/dist/providers/openai-provider.d.ts +1 -1
- package/dist/providers/openai-provider.js +3 -2
- package/dist/settings.d.ts +1 -0
- package/dist/settings.js +2 -1
- package/dist/skills/engine.d.ts +10 -0
- package/dist/skills/engine.js +75 -0
- package/dist/skills/fix-skill.d.ts +2 -0
- package/dist/skills/fix-skill.js +38 -0
- package/dist/skills/generate-artifact-skill.d.ts +2 -0
- package/dist/skills/generate-artifact-skill.js +32 -0
- package/dist/skills/generate-pipeline-skill.d.ts +2 -0
- package/dist/skills/generate-pipeline-skill.js +45 -0
- package/dist/skills/normalize-skill.d.ts +2 -0
- package/dist/skills/normalize-skill.js +29 -0
- package/dist/skills/types.d.ts +28 -0
- package/dist/skills/types.js +2 -0
- package/dist/skills/validate-skill.d.ts +2 -0
- package/dist/skills/validate-skill.js +29 -0
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +2 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +13 -0
- package/dist/validator.js +0 -4
- package/dist/workflow-commands.js +2 -1
- package/package.json +74 -45
- package/presets/fintech/preset.json +48 -1
- package/presets/fintech/prompts/prd.md +99 -2
- package/presets/marketplace/preset.json +51 -1
- package/presets/marketplace/prompts/prd.md +140 -2
- package/presets/saas/preset.json +53 -1
- package/presets/saas/prompts/prd.md +150 -2
- package/src/agents/agent-registry.ts +93 -0
- package/src/agents/anthropic/index.ts +86 -0
- package/src/agents/anthropic/manifest.json +7 -0
- package/src/agents/base.ts +77 -0
- package/src/agents/google/index.ts +79 -0
- package/src/agents/google/manifest.json +7 -0
- package/src/agents/mock/index.ts +32 -0
- package/src/agents/mock/manifest.json +7 -0
- package/src/agents/openai/index.ts +83 -0
- package/src/agents/openai/manifest.json +7 -0
- package/src/agents/system-prompts.ts +35 -0
- package/src/{agent-command-installer.ts → cli/agent-command-installer.ts} +164 -164
- package/src/{agents.ts → cli/agent-ids.ts} +58 -56
- package/src/{doctor.ts → cli/doctor.ts} +157 -137
- package/src/cli/fix-tui.ts +111 -0
- package/src/{cli.ts → cli/index.ts} +459 -319
- package/src/{init-tui.ts → cli/init-tui.ts} +208 -179
- package/src/{init.ts → cli/init.ts} +398 -391
- package/src/cli/normalize-interactive.ts +241 -0
- package/src/{preset-loader.ts → cli/preset-loader.ts} +237 -237
- package/src/{artifact-registry.ts → core/artifact-registry.ts} +69 -69
- package/src/{artifacts.ts → core/artifacts.ts} +1081 -777
- package/src/core/clean.ts +88 -0
- package/src/{consistency.ts → core/consistency.ts} +374 -303
- package/src/{constants.ts → core/constants.ts} +72 -72
- package/src/{errors.ts → core/errors.ts} +7 -7
- package/src/core/fix.ts +253 -0
- package/src/{hook-executor.ts → core/hook-executor.ts} +196 -196
- package/src/{markdown.ts → core/markdown.ts} +93 -73
- package/src/core/normalize.ts +145 -0
- package/src/{normalized-brief.ts → core/normalized-brief.ts} +227 -206
- package/src/{output-index.ts → core/output-index.ts} +59 -59
- package/src/{paths.ts → core/paths.ts} +75 -71
- package/src/{project-config.ts → core/project-config.ts} +78 -78
- package/src/{registry.ts → core/registry.ts} +119 -119
- package/src/{settings.ts → core/settings.ts} +35 -34
- package/src/core/template-engine.ts +45 -0
- package/src/{template-resolver.ts → core/template-resolver.ts} +54 -54
- package/src/{templates.ts → core/templates.ts} +452 -450
- package/src/core/terminology.ts +177 -0
- package/src/core/tracing.ts +110 -0
- package/src/{types.ts → core/types.ts} +46 -46
- package/src/{utils.ts → core/utils.ts} +64 -50
- package/src/{validate.ts → core/validate.ts} +252 -246
- package/src/{validator.ts → core/validator.ts} +92 -96
- package/src/{version.ts → core/version.ts} +24 -24
- package/src/{workflow-commands.ts → core/workflow-commands.ts} +32 -31
- package/src/i18n/en.json +45 -0
- package/src/i18n/index.ts +58 -0
- package/src/i18n/tr.json +45 -0
- package/src/providers/index.ts +29 -12
- package/src/providers/mock-provider.ts +200 -199
- package/src/providers/openai-provider.ts +88 -87
- package/src/skills/engine.ts +94 -0
- package/src/skills/fix-skill.ts +38 -0
- package/src/skills/generate-artifact-skill.ts +32 -0
- package/src/skills/generate-pipeline-skill.ts +49 -0
- package/src/skills/normalize-skill.ts +29 -0
- package/src/skills/types.ts +36 -0
- package/src/skills/validate-skill.ts +29 -0
- package/templates/commands/prodo-fix.md +46 -0
- package/templates/commands/prodo-normalize.md +118 -23
- package/templates/commands/prodo-prd.md +138 -17
- package/templates/commands/prodo-stories.md +153 -17
- package/templates/commands/prodo-techspec.md +167 -17
- package/templates/commands/prodo-validate.md +184 -26
- package/templates/commands/prodo-wireframe.md +188 -17
- package/templates/commands/prodo-workflow.md +200 -17
- package/src/normalize.ts +0 -89
|
@@ -0,0 +1,46 @@
|
|
|
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.resolveTemplate = resolveTemplate;
|
|
7
|
+
exports.resolveCompanionTemplate = resolveCompanionTemplate;
|
|
8
|
+
exports.extractRequiredHeadingsFromTemplate = extractRequiredHeadingsFromTemplate;
|
|
9
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
10
|
+
const paths_1 = require("./paths");
|
|
11
|
+
const markdown_1 = require("./markdown");
|
|
12
|
+
const utils_1 = require("./utils");
|
|
13
|
+
async function resolveTemplate(options) {
|
|
14
|
+
const { cwd, artifactType } = options;
|
|
15
|
+
const candidates = [
|
|
16
|
+
...(0, paths_1.overrideTemplateCandidatePaths)(cwd, artifactType),
|
|
17
|
+
...(0, paths_1.templateCandidatePaths)(cwd, artifactType)
|
|
18
|
+
];
|
|
19
|
+
for (const filePath of candidates) {
|
|
20
|
+
if (await (0, utils_1.fileExists)(filePath)) {
|
|
21
|
+
const content = await promises_1.default.readFile(filePath, "utf8");
|
|
22
|
+
return { path: filePath, content };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
async function resolveCompanionTemplate(options) {
|
|
28
|
+
const { cwd, artifactType } = options;
|
|
29
|
+
const nativeExt = artifactType === "workflow" ? ".mmd" : artifactType === "wireframe" ? ".html" : null;
|
|
30
|
+
if (!nativeExt)
|
|
31
|
+
return null;
|
|
32
|
+
const candidates = [
|
|
33
|
+
...(0, paths_1.overrideTemplateCandidatePaths)(cwd, artifactType),
|
|
34
|
+
...(0, paths_1.templateCandidatePaths)(cwd, artifactType)
|
|
35
|
+
].filter((candidate) => candidate.toLowerCase().endsWith(nativeExt));
|
|
36
|
+
for (const filePath of candidates) {
|
|
37
|
+
if (await (0, utils_1.fileExists)(filePath)) {
|
|
38
|
+
const content = await promises_1.default.readFile(filePath, "utf8");
|
|
39
|
+
return { path: filePath, content };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function extractRequiredHeadingsFromTemplate(content) {
|
|
45
|
+
return (0, markdown_1.extractRequiredHeadings)(content);
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ArtifactType } from "./types";
|
|
2
|
+
import type { WorkflowCommand } from "./workflow-commands";
|
|
3
|
+
export declare function schemaTemplate(artifactType: ArtifactType): Record<string, unknown>;
|
|
4
|
+
export declare function promptTemplate(artifactType: ArtifactType, lang?: string): string;
|
|
5
|
+
export declare function artifactTemplateTemplate(artifactType: ArtifactType, lang?: string): string;
|
|
6
|
+
export declare const START_BRIEF_TEMPLATE = "# Product Brief\n\n## Product Name\nExample Product\n\n## Problem\nDescribe the user problem.\n\n## Audience\nWho this product is for.\n\n## Core Features\n- Feature A\n- Feature B\n\n## Goals\n- Goal 1\n- Goal 2\n\n## Constraints\n- Budget or timeline constraints\n- Compliance or technical constraints\n";
|
|
7
|
+
export declare const NORMALIZED_BRIEF_TEMPLATE: {
|
|
8
|
+
schema_version: string;
|
|
9
|
+
product_name: string;
|
|
10
|
+
problem: string;
|
|
11
|
+
audience: string[];
|
|
12
|
+
goals: string[];
|
|
13
|
+
core_features: string[];
|
|
14
|
+
constraints: string[];
|
|
15
|
+
assumptions: string[];
|
|
16
|
+
contracts: {
|
|
17
|
+
goals: {
|
|
18
|
+
id: string;
|
|
19
|
+
text: string;
|
|
20
|
+
}[];
|
|
21
|
+
core_features: {
|
|
22
|
+
id: string;
|
|
23
|
+
text: string;
|
|
24
|
+
}[];
|
|
25
|
+
constraints: {
|
|
26
|
+
id: string;
|
|
27
|
+
text: string;
|
|
28
|
+
}[];
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export declare const NORMALIZE_PROMPT_TEMPLATE = "Normalize start-brief content into JSON.\n\nReturn JSON object with keys:\n- schema_version (string)\n- product_name (string)\n- problem (string)\n- audience (string[])\n- goals (string[])\n- core_features (string[])\n- constraints (string[])\n- assumptions (string[])\n- contracts.goals[] ({id,text})\n- contracts.core_features[] ({id,text})\n- contracts.constraints[] ({id,text})\n- confidence.product_name (0..1)\n- confidence.problem (0..1)\n- confidence.audience (0..1)\n- confidence.goals (0..1)\n- confidence.core_features (0..1)\n\nRules:\n- do NOT invent missing critical content\n- keep wording concise and concrete\n- preserve original language and Unicode characters exactly from brief\n- never transliterate Turkish letters (\u00E7, \u011F, \u0131, \u0130, \u00F6, \u015F, \u00FC) into ASCII\n- if critical field is missing, return empty and low confidence (<0.7)\n- assign deterministic IDs: goals => G1..Gn, features => F1..Fn, constraints => C1..Cn\n- input files are read-only; never modify, summarize, or rewrite `brief.md` in-place\n- write normalized output as a new JSON object only";
|
|
32
|
+
export declare function commandTemplate(command: WorkflowCommand): string;
|
|
33
|
+
export declare const HOOKS_TEMPLATE = "# Hook item fields:\n# - command: string (required)\n# - optional: boolean (default false)\n# - enabled: boolean (default true)\n# - condition: shell command; run hook only if condition exits 0\n# - timeout_ms: per-attempt timeout in milliseconds (default 30000)\n# - retry: extra retry count after first attempt (default 0)\n# - retry_delay_ms: delay between retries (default 500)\n#\n# Example:\n# hooks:\n# before_prd:\n# - command: \"node -e \\\"console.log('lint ok')\\\"\"\n# optional: false\n# enabled: true\n# condition: \"node -e \\\"process.exit(0)\\\"\"\n# timeout_ms: 15000\n# retry: 1\n# retry_delay_ms: 300\n\nhooks:\n before_normalize: []\n after_normalize: []\n before_prd: []\n after_prd: []\n before_workflow: []\n after_workflow: []\n before_wireframe: []\n after_wireframe: []\n before_stories: []\n after_stories: []\n before_techspec: []\n after_techspec: []\n before_validate: []\n after_validate: []\n";
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HOOKS_TEMPLATE = exports.NORMALIZE_PROMPT_TEMPLATE = exports.NORMALIZED_BRIEF_TEMPLATE = exports.START_BRIEF_TEMPLATE = void 0;
|
|
4
|
+
exports.schemaTemplate = schemaTemplate;
|
|
5
|
+
exports.promptTemplate = promptTemplate;
|
|
6
|
+
exports.artifactTemplateTemplate = artifactTemplateTemplate;
|
|
7
|
+
exports.commandTemplate = commandTemplate;
|
|
8
|
+
const constants_1 = require("./constants");
|
|
9
|
+
const BASE_SCHEMA = {
|
|
10
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11
|
+
type: "object",
|
|
12
|
+
required: ["frontmatter", "body"],
|
|
13
|
+
properties: {
|
|
14
|
+
frontmatter: {
|
|
15
|
+
type: "object",
|
|
16
|
+
required: ["artifact_type", "version", "source_brief", "generated_at", "status"],
|
|
17
|
+
properties: {
|
|
18
|
+
artifact_type: { type: "string" },
|
|
19
|
+
version: { type: "string", minLength: 1 },
|
|
20
|
+
source_brief: { type: "string", minLength: 1 },
|
|
21
|
+
generated_at: { type: "string", format: "date-time" },
|
|
22
|
+
status: { type: "string", enum: ["draft", "reviewed", "approved"] },
|
|
23
|
+
title: { type: "string" },
|
|
24
|
+
upstream_artifacts: {
|
|
25
|
+
type: "array",
|
|
26
|
+
items: { type: "string" }
|
|
27
|
+
},
|
|
28
|
+
contract_coverage: {
|
|
29
|
+
type: "object",
|
|
30
|
+
required: ["goals", "core_features", "constraints"],
|
|
31
|
+
properties: {
|
|
32
|
+
goals: { type: "array", items: { type: "string", pattern: "^G[0-9]+$" } },
|
|
33
|
+
core_features: { type: "array", items: { type: "string", pattern: "^F[0-9]+$" } },
|
|
34
|
+
constraints: { type: "array", items: { type: "string", pattern: "^C[0-9]+$" } }
|
|
35
|
+
},
|
|
36
|
+
additionalProperties: false
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
additionalProperties: true
|
|
40
|
+
},
|
|
41
|
+
body: {
|
|
42
|
+
type: "string",
|
|
43
|
+
minLength: 40
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
additionalProperties: false
|
|
47
|
+
};
|
|
48
|
+
function schemaTemplate(artifactType) {
|
|
49
|
+
return {
|
|
50
|
+
...BASE_SCHEMA,
|
|
51
|
+
properties: {
|
|
52
|
+
...BASE_SCHEMA.properties,
|
|
53
|
+
frontmatter: {
|
|
54
|
+
...BASE_SCHEMA.properties.frontmatter,
|
|
55
|
+
properties: {
|
|
56
|
+
...BASE_SCHEMA.properties.frontmatter
|
|
57
|
+
.properties,
|
|
58
|
+
artifact_type: { const: artifactType }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
x_required_headings: (0, constants_1.defaultRequiredHeadings)(artifactType)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function promptTemplate(artifactType, lang = "en") {
|
|
66
|
+
const tr = lang.toLowerCase().startsWith("tr");
|
|
67
|
+
if (artifactType === "workflow") {
|
|
68
|
+
return `You are Prodo's artifact generator for WORKFLOW.
|
|
69
|
+
|
|
70
|
+
Goal:
|
|
71
|
+
- Produce paired workflow outputs for the same flow.
|
|
72
|
+
|
|
73
|
+
Output contract (STRICT):
|
|
74
|
+
- Return Markdown explanation first (no frontmatter), following required headings exactly.
|
|
75
|
+
- Include actionable flow details and contract tags like [F1].
|
|
76
|
+
- Then append a mandatory Mermaid block for the same flow:
|
|
77
|
+
\`\`\`mermaid
|
|
78
|
+
flowchart TD
|
|
79
|
+
A --> B
|
|
80
|
+
\`\`\`
|
|
81
|
+
- Do not return prose-only markdown without Mermaid.
|
|
82
|
+
|
|
83
|
+
Input immutability:
|
|
84
|
+
- Input files are read-only.
|
|
85
|
+
- Do not modify, rewrite, summarize, or optimize \`brief.md\`.
|
|
86
|
+
|
|
87
|
+
Language contract:
|
|
88
|
+
- Output text in requested language from inputContext.outputLanguage.
|
|
89
|
+
- If outputLanguage is "tr", every sentence and heading MUST be Turkish.
|
|
90
|
+
- Never mix Turkish and English in the same artifact.
|
|
91
|
+
|
|
92
|
+
Quality bar:
|
|
93
|
+
- Main flow must be implementation-ready.
|
|
94
|
+
- Edge cases and postconditions must be explicit.`;
|
|
95
|
+
}
|
|
96
|
+
if (artifactType === "wireframe") {
|
|
97
|
+
return `You are Prodo's artifact generator for WIREFRAME.
|
|
98
|
+
|
|
99
|
+
Goal:
|
|
100
|
+
- Produce a human-readable wireframe explanation in Markdown.
|
|
101
|
+
|
|
102
|
+
Output contract (STRICT):
|
|
103
|
+
- Return Markdown body only (no frontmatter).
|
|
104
|
+
- Include all required headings exactly as provided.
|
|
105
|
+
- Describe one concrete screen at a time with actionable details.
|
|
106
|
+
- HTML wireframe is generated by Prodo as a companion .html file; do not output raw HTML as primary markdown body.
|
|
107
|
+
|
|
108
|
+
Input immutability:
|
|
109
|
+
- Input files are read-only.
|
|
110
|
+
- Do not modify, rewrite, summarize, or optimize \`brief.md\`.
|
|
111
|
+
|
|
112
|
+
Language contract:
|
|
113
|
+
- Output text in requested language from inputContext.outputLanguage.
|
|
114
|
+
- If outputLanguage is "tr", every sentence and heading MUST be Turkish.
|
|
115
|
+
- Never mix Turkish and English in the same artifact.
|
|
116
|
+
|
|
117
|
+
Quality bar:
|
|
118
|
+
- Keep sections concise but specific.
|
|
119
|
+
- Ensure actions/states/messages are testable and implementation-relevant.`;
|
|
120
|
+
}
|
|
121
|
+
return `You are Prodo's artifact generator for ${artifactType.toUpperCase()}.
|
|
122
|
+
|
|
123
|
+
Goal:
|
|
124
|
+
- Produce a decision-ready product artifact from normalized brief and upstream artifacts.
|
|
125
|
+
|
|
126
|
+
Output contract:
|
|
127
|
+
- Return only Markdown body (NO YAML frontmatter).
|
|
128
|
+
- Keep all required section headings exactly as provided.
|
|
129
|
+
- When referencing brief contracts, use explicit tags like [G1], [F2], [C1].
|
|
130
|
+
- Use concrete statements, avoid placeholders like "TBD" unless unavoidable.
|
|
131
|
+
- Reference upstream constraints and tradeoffs explicitly.
|
|
132
|
+
|
|
133
|
+
Input immutability:
|
|
134
|
+
- Input files are read-only.
|
|
135
|
+
- Do not modify, rewrite, summarize, or optimize \`brief.md\`.
|
|
136
|
+
|
|
137
|
+
Language contract:
|
|
138
|
+
- Output text in requested language from inputContext.outputLanguage.
|
|
139
|
+
- If outputLanguage is "tr", every sentence and heading MUST be Turkish.
|
|
140
|
+
- Never mix Turkish and English in the same artifact.
|
|
141
|
+
- Do not translate or change required headings unless outputLanguage is "tr" and Turkish headings are provided.
|
|
142
|
+
|
|
143
|
+
Quality bar:
|
|
144
|
+
- Every section must contain actionable content.
|
|
145
|
+
- Include assumptions and risks where relevant.
|
|
146
|
+
- Keep content concise but implementation-ready.
|
|
147
|
+
|
|
148
|
+
Required headings:
|
|
149
|
+
${(0, constants_1.defaultRequiredHeadings)(artifactType).map((heading) => `- ${heading}`).join("\n")}
|
|
150
|
+
|
|
151
|
+
When data is missing:
|
|
152
|
+
${tr ? "- varsayim yaparken Turkce yaz\n- varsayimi ilgili bolumde acikca belirt" : "- infer a sensible default\n- state the assumption clearly in the relevant section"}`;
|
|
153
|
+
}
|
|
154
|
+
function artifactTemplateTemplate(artifactType, lang = "en") {
|
|
155
|
+
const tr = lang.toLowerCase().startsWith("tr");
|
|
156
|
+
const base = [`# ${artifactType.toUpperCase()} Template`, ""];
|
|
157
|
+
if (artifactType === "prd") {
|
|
158
|
+
return [
|
|
159
|
+
...base,
|
|
160
|
+
"## Problem",
|
|
161
|
+
"- User pain and why now",
|
|
162
|
+
"- Existing workaround and gap",
|
|
163
|
+
"",
|
|
164
|
+
"## Goals",
|
|
165
|
+
"- Business goals (measurable)",
|
|
166
|
+
"- User outcomes",
|
|
167
|
+
"",
|
|
168
|
+
"## Scope",
|
|
169
|
+
"- In scope",
|
|
170
|
+
"- Out of scope",
|
|
171
|
+
"",
|
|
172
|
+
"## Requirements",
|
|
173
|
+
"- Functional requirements",
|
|
174
|
+
"- Non-functional requirements",
|
|
175
|
+
"- Compliance/security requirements",
|
|
176
|
+
"",
|
|
177
|
+
"## Risks",
|
|
178
|
+
"- Top risks and mitigations",
|
|
179
|
+
"",
|
|
180
|
+
"## Assumptions",
|
|
181
|
+
"- Key assumptions"
|
|
182
|
+
].join("\n");
|
|
183
|
+
}
|
|
184
|
+
if (artifactType === "workflow") {
|
|
185
|
+
return [
|
|
186
|
+
"flowchart TD",
|
|
187
|
+
` A[${tr ? "Baslangic" : "Start"}] --> B[[F1] ${tr ? "Kullanici islemi" : "User action"}]`,
|
|
188
|
+
` B --> C[${tr ? "Sistem islemi" : "System process"}]`,
|
|
189
|
+
` C --> D[${tr ? "Basari" : "Success"}]`,
|
|
190
|
+
` C --> E[${tr ? "Hata" : "Error"}]`
|
|
191
|
+
].join("\n");
|
|
192
|
+
}
|
|
193
|
+
if (artifactType === "wireframe") {
|
|
194
|
+
return `<!doctype html>
|
|
195
|
+
<html lang="${tr ? "tr" : "en"}">
|
|
196
|
+
<head>
|
|
197
|
+
<meta charset="utf-8" />
|
|
198
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
199
|
+
<title>${tr ? "Wireframe Ekrani" : "Wireframe Screen"}</title>
|
|
200
|
+
</head>
|
|
201
|
+
<body>
|
|
202
|
+
<header>
|
|
203
|
+
<h1>${tr ? "Ekran Basligi" : "Screen Title"}</h1>
|
|
204
|
+
<nav>
|
|
205
|
+
<button type="button">${tr ? "Geri" : "Back"}</button>
|
|
206
|
+
<button type="button">${tr ? "Devam" : "Next"}</button>
|
|
207
|
+
</nav>
|
|
208
|
+
</header>
|
|
209
|
+
<main>
|
|
210
|
+
<section>
|
|
211
|
+
<h2>${tr ? "Birincil Icerik" : "Primary Content"}</h2>
|
|
212
|
+
<ul>
|
|
213
|
+
<li>${tr ? "Durum ozeti" : "Status summary"}</li>
|
|
214
|
+
<li>${tr ? "Aksiyon alani" : "Action area"}</li>
|
|
215
|
+
</ul>
|
|
216
|
+
</section>
|
|
217
|
+
<section>
|
|
218
|
+
<h2>${tr ? "Form" : "Form"}</h2>
|
|
219
|
+
<form>
|
|
220
|
+
<label>${tr ? "Alan" : "Field"} <input type="text" /></label>
|
|
221
|
+
<button type="submit">${tr ? "Kaydet" : "Save"}</button>
|
|
222
|
+
</form>
|
|
223
|
+
</section>
|
|
224
|
+
</main>
|
|
225
|
+
</body>
|
|
226
|
+
</html>`;
|
|
227
|
+
}
|
|
228
|
+
if (artifactType === "stories") {
|
|
229
|
+
return [
|
|
230
|
+
...base,
|
|
231
|
+
"## User Stories",
|
|
232
|
+
"- As a <user>, I want <goal>, so that <benefit>",
|
|
233
|
+
"",
|
|
234
|
+
"## Acceptance Criteria",
|
|
235
|
+
"- Given / When / Then criteria",
|
|
236
|
+
"- Negative and edge scenarios",
|
|
237
|
+
"",
|
|
238
|
+
"## Traceability",
|
|
239
|
+
"- Map each story to PRD requirement"
|
|
240
|
+
].join("\n");
|
|
241
|
+
}
|
|
242
|
+
if (artifactType === "techspec") {
|
|
243
|
+
return [
|
|
244
|
+
...base,
|
|
245
|
+
"## Architecture",
|
|
246
|
+
"- High-level components and boundaries",
|
|
247
|
+
"",
|
|
248
|
+
"## Data Model",
|
|
249
|
+
"- Entities, fields, relations",
|
|
250
|
+
"",
|
|
251
|
+
"## APIs",
|
|
252
|
+
"- Endpoints/interfaces and contracts",
|
|
253
|
+
"",
|
|
254
|
+
"## Risks",
|
|
255
|
+
"- Technical risks and mitigation",
|
|
256
|
+
"",
|
|
257
|
+
"## Observability",
|
|
258
|
+
"- Logs, metrics, alerts"
|
|
259
|
+
].join("\n");
|
|
260
|
+
}
|
|
261
|
+
const fallbackTemplate = [
|
|
262
|
+
...base,
|
|
263
|
+
tr ? "## Ozet" : "## Summary",
|
|
264
|
+
tr ? "- Ana amac ve beklenen cikti" : "- Core intent and outcomes",
|
|
265
|
+
"",
|
|
266
|
+
tr ? "## Detaylar" : "## Details",
|
|
267
|
+
tr ? "- Kararlar, varsayimlar ve riskler" : "- Key decisions, assumptions, and risks",
|
|
268
|
+
"",
|
|
269
|
+
tr ? "## Izlenebilirlik" : "## Traceability",
|
|
270
|
+
tr ? "- Kararlari kontrat etiketleri ile eslestir" : "- Link decisions to contract tags"
|
|
271
|
+
].join("\n");
|
|
272
|
+
return fallbackTemplate;
|
|
273
|
+
}
|
|
274
|
+
exports.START_BRIEF_TEMPLATE = `# Product Brief
|
|
275
|
+
|
|
276
|
+
## Product Name
|
|
277
|
+
Example Product
|
|
278
|
+
|
|
279
|
+
## Problem
|
|
280
|
+
Describe the user problem.
|
|
281
|
+
|
|
282
|
+
## Audience
|
|
283
|
+
Who this product is for.
|
|
284
|
+
|
|
285
|
+
## Core Features
|
|
286
|
+
- Feature A
|
|
287
|
+
- Feature B
|
|
288
|
+
|
|
289
|
+
## Goals
|
|
290
|
+
- Goal 1
|
|
291
|
+
- Goal 2
|
|
292
|
+
|
|
293
|
+
## Constraints
|
|
294
|
+
- Budget or timeline constraints
|
|
295
|
+
- Compliance or technical constraints
|
|
296
|
+
`;
|
|
297
|
+
exports.NORMALIZED_BRIEF_TEMPLATE = {
|
|
298
|
+
schema_version: "1.0",
|
|
299
|
+
product_name: "Example Product",
|
|
300
|
+
problem: "Describe the user problem clearly.",
|
|
301
|
+
audience: ["Primary users"],
|
|
302
|
+
goals: ["Goal 1", "Goal 2"],
|
|
303
|
+
core_features: ["Feature A", "Feature B"],
|
|
304
|
+
constraints: ["Constraint 1"],
|
|
305
|
+
assumptions: ["Assumption 1"],
|
|
306
|
+
contracts: {
|
|
307
|
+
goals: [
|
|
308
|
+
{ id: "G1", text: "Goal 1" },
|
|
309
|
+
{ id: "G2", text: "Goal 2" }
|
|
310
|
+
],
|
|
311
|
+
core_features: [
|
|
312
|
+
{ id: "F1", text: "Feature A" },
|
|
313
|
+
{ id: "F2", text: "Feature B" }
|
|
314
|
+
],
|
|
315
|
+
constraints: [{ id: "C1", text: "Constraint 1" }]
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
exports.NORMALIZE_PROMPT_TEMPLATE = `Normalize start-brief content into JSON.
|
|
319
|
+
|
|
320
|
+
Return JSON object with keys:
|
|
321
|
+
- schema_version (string)
|
|
322
|
+
- product_name (string)
|
|
323
|
+
- problem (string)
|
|
324
|
+
- audience (string[])
|
|
325
|
+
- goals (string[])
|
|
326
|
+
- core_features (string[])
|
|
327
|
+
- constraints (string[])
|
|
328
|
+
- assumptions (string[])
|
|
329
|
+
- contracts.goals[] ({id,text})
|
|
330
|
+
- contracts.core_features[] ({id,text})
|
|
331
|
+
- contracts.constraints[] ({id,text})
|
|
332
|
+
- confidence.product_name (0..1)
|
|
333
|
+
- confidence.problem (0..1)
|
|
334
|
+
- confidence.audience (0..1)
|
|
335
|
+
- confidence.goals (0..1)
|
|
336
|
+
- confidence.core_features (0..1)
|
|
337
|
+
|
|
338
|
+
Rules:
|
|
339
|
+
- do NOT invent missing critical content
|
|
340
|
+
- keep wording concise and concrete
|
|
341
|
+
- preserve original language and Unicode characters exactly from brief
|
|
342
|
+
- never transliterate Turkish letters (ç, ğ, ı, İ, ö, ş, ü) into ASCII
|
|
343
|
+
- if critical field is missing, return empty and low confidence (<0.7)
|
|
344
|
+
- assign deterministic IDs: goals => G1..Gn, features => F1..Fn, constraints => C1..Cn
|
|
345
|
+
- input files are read-only; never modify, summarize, or rewrite \`brief.md\` in-place
|
|
346
|
+
- write normalized output as a new JSON object only`;
|
|
347
|
+
function commandTemplate(command) {
|
|
348
|
+
const normalizeJsonGuard = command.cliSubcommand === "normalize"
|
|
349
|
+
? `
|
|
350
|
+
- Normalize output format check:
|
|
351
|
+
- \`.prodo/briefs/normalized-brief.json\` must be strict JSON object (no markdown fences).
|
|
352
|
+
- If invalid, rewrite file as pure JSON object only.`
|
|
353
|
+
: "";
|
|
354
|
+
return `---
|
|
355
|
+
description: ${command.description}
|
|
356
|
+
handoffs:
|
|
357
|
+
- label: Continue Workflow
|
|
358
|
+
agent: prodo-next
|
|
359
|
+
prompt: Continue with the next Prodo command in sequence.
|
|
360
|
+
send: true
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## User Input
|
|
364
|
+
|
|
365
|
+
\`\`\`text
|
|
366
|
+
$ARGUMENTS
|
|
367
|
+
\`\`\`
|
|
368
|
+
|
|
369
|
+
Execution policy:
|
|
370
|
+
- Execute-first, diagnose-second.
|
|
371
|
+
- Perform only minimal prerequisite checks before execution.
|
|
372
|
+
- Do not run shell commands or CLI commands from inside the agent.
|
|
373
|
+
- Never run \`prodo-${command.cliSubcommand}\`, \`prodo ${command.cliSubcommand}\`, or \`prodo ...\` in shell.
|
|
374
|
+
- Do not inspect hooks or internals unless command execution fails.
|
|
375
|
+
- Input files are read-only; never modify or rewrite \`brief.md\`.
|
|
376
|
+
- Never print full artifact content in chat.
|
|
377
|
+
- Write/update files first, then reply with short status + written file path(s).
|
|
378
|
+
|
|
379
|
+
## Execution
|
|
380
|
+
|
|
381
|
+
1. Minimal prerequisites only:
|
|
382
|
+
- Confirm project is initialized (\`.prodo/\` exists).
|
|
383
|
+
- Confirm required input files for \`${command.cliSubcommand}\` exist.
|
|
384
|
+
- Confirm output location is writable.
|
|
385
|
+
|
|
386
|
+
2. Execute immediately:
|
|
387
|
+
- Execute the \`${command.cliSubcommand}\` process directly using workspace files and Prodo rules.
|
|
388
|
+
- Keep narration short and action-oriented.
|
|
389
|
+
|
|
390
|
+
3. Verify result:
|
|
391
|
+
- Confirm expected output file(s) were created/updated under \`product-docs/\` (or \`.prodo/briefs\` for normalize).
|
|
392
|
+
- Confirm command success state (exit code or validation status).
|
|
393
|
+
- Confirm \`brief.md\` hash/content did not change.
|
|
394
|
+
- Do NOT create manual fallback files under \`.prodo/artifact\` or any ad-hoc folder.
|
|
395
|
+
${normalizeJsonGuard}
|
|
396
|
+
|
|
397
|
+
4. Diagnose only on failure:
|
|
398
|
+
- Inspect \`.prodo/hooks.yml\` only after execution failure.
|
|
399
|
+
- Explain root cause and next fix steps briefly.
|
|
400
|
+
|
|
401
|
+
## Handoff
|
|
402
|
+
|
|
403
|
+
Suggest the next slash command explicitly (for example: \`/prodo-prd\`, \`/prodo-workflow\`).`;
|
|
404
|
+
}
|
|
405
|
+
exports.HOOKS_TEMPLATE = `# Hook item fields:
|
|
406
|
+
# - command: string (required)
|
|
407
|
+
# - optional: boolean (default false)
|
|
408
|
+
# - enabled: boolean (default true)
|
|
409
|
+
# - condition: shell command; run hook only if condition exits 0
|
|
410
|
+
# - timeout_ms: per-attempt timeout in milliseconds (default 30000)
|
|
411
|
+
# - retry: extra retry count after first attempt (default 0)
|
|
412
|
+
# - retry_delay_ms: delay between retries (default 500)
|
|
413
|
+
#
|
|
414
|
+
# Example:
|
|
415
|
+
# hooks:
|
|
416
|
+
# before_prd:
|
|
417
|
+
# - command: "node -e \\"console.log('lint ok')\\""
|
|
418
|
+
# optional: false
|
|
419
|
+
# enabled: true
|
|
420
|
+
# condition: "node -e \\"process.exit(0)\\""
|
|
421
|
+
# timeout_ms: 15000
|
|
422
|
+
# retry: 1
|
|
423
|
+
# retry_delay_ms: 300
|
|
424
|
+
|
|
425
|
+
hooks:
|
|
426
|
+
before_normalize: []
|
|
427
|
+
after_normalize: []
|
|
428
|
+
before_prd: []
|
|
429
|
+
after_prd: []
|
|
430
|
+
before_workflow: []
|
|
431
|
+
after_workflow: []
|
|
432
|
+
before_wireframe: []
|
|
433
|
+
after_wireframe: []
|
|
434
|
+
before_stories: []
|
|
435
|
+
after_stories: []
|
|
436
|
+
before_techspec: []
|
|
437
|
+
after_techspec: []
|
|
438
|
+
before_validate: []
|
|
439
|
+
after_validate: []
|
|
440
|
+
`;
|
|
@@ -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 TermEntry = {
|
|
9
|
+
term: string;
|
|
10
|
+
normalizedTerm: string;
|
|
11
|
+
definition?: string;
|
|
12
|
+
sources: Array<{
|
|
13
|
+
artifactType: ArtifactType | "brief";
|
|
14
|
+
file: string;
|
|
15
|
+
heading?: string;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
18
|
+
export type TermMap = Map<string, TermEntry>;
|
|
19
|
+
export declare function buildTermMap(normalizedBrief: NormalizedBrief, loadedArtifacts: LoadedArtifact[]): TermMap;
|
|
20
|
+
export declare function checkTermReconciliation(termMap: TermMap): ValidationIssue[];
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildTermMap = buildTermMap;
|
|
4
|
+
exports.checkTermReconciliation = checkTermReconciliation;
|
|
5
|
+
const markdown_1 = require("./markdown");
|
|
6
|
+
function normalizeTerm(term) {
|
|
7
|
+
return term
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[^a-z0-9\u00c0-\u024f\s]/g, "")
|
|
10
|
+
.replace(/\s+/g, " ")
|
|
11
|
+
.trim();
|
|
12
|
+
}
|
|
13
|
+
function extractBoldTerms(body) {
|
|
14
|
+
const matches = body.match(/\*\*([^*]+)\*\*/g) ?? [];
|
|
15
|
+
return matches
|
|
16
|
+
.map((m) => m.replace(/\*\*/g, "").trim())
|
|
17
|
+
.filter((t) => t.length > 2 && t.length < 60);
|
|
18
|
+
}
|
|
19
|
+
function buildTermMap(normalizedBrief, loadedArtifacts) {
|
|
20
|
+
const termMap = new Map();
|
|
21
|
+
function addTerm(term, source, definition) {
|
|
22
|
+
const normalized = normalizeTerm(term);
|
|
23
|
+
if (!normalized || normalized.length < 2)
|
|
24
|
+
return;
|
|
25
|
+
const existing = termMap.get(normalized);
|
|
26
|
+
if (existing) {
|
|
27
|
+
existing.sources.push(source);
|
|
28
|
+
if (definition && !existing.definition) {
|
|
29
|
+
existing.definition = definition;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
termMap.set(normalized, {
|
|
34
|
+
term,
|
|
35
|
+
normalizedTerm: normalized,
|
|
36
|
+
definition,
|
|
37
|
+
sources: [source]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
addTerm(normalizedBrief.product_name, { artifactType: "brief", file: "brief.md" });
|
|
42
|
+
for (const goal of normalizedBrief.goals) {
|
|
43
|
+
addTerm(goal, { artifactType: "brief", file: "brief.md" }, goal);
|
|
44
|
+
}
|
|
45
|
+
for (const feature of normalizedBrief.core_features) {
|
|
46
|
+
addTerm(feature, { artifactType: "brief", file: "brief.md" }, feature);
|
|
47
|
+
}
|
|
48
|
+
for (const constraint of normalizedBrief.constraints) {
|
|
49
|
+
addTerm(constraint, { artifactType: "brief", file: "brief.md" }, constraint);
|
|
50
|
+
}
|
|
51
|
+
for (const contract of normalizedBrief.contracts.goals) {
|
|
52
|
+
addTerm(contract.text, { artifactType: "brief", file: "brief.md" }, contract.text);
|
|
53
|
+
}
|
|
54
|
+
for (const contract of normalizedBrief.contracts.core_features) {
|
|
55
|
+
addTerm(contract.text, { artifactType: "brief", file: "brief.md" }, contract.text);
|
|
56
|
+
}
|
|
57
|
+
for (const contract of normalizedBrief.contracts.constraints) {
|
|
58
|
+
addTerm(contract.text, { artifactType: "brief", file: "brief.md" }, contract.text);
|
|
59
|
+
}
|
|
60
|
+
for (const artifact of loadedArtifacts) {
|
|
61
|
+
const sections = (0, markdown_1.parseMarkdownSections)(artifact.doc.body);
|
|
62
|
+
for (const section of sections) {
|
|
63
|
+
if (section.level === 2) {
|
|
64
|
+
addTerm(section.heading, {
|
|
65
|
+
artifactType: artifact.type,
|
|
66
|
+
file: artifact.file,
|
|
67
|
+
heading: section.heading
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const boldTerms = extractBoldTerms(artifact.doc.body);
|
|
72
|
+
for (const boldTerm of boldTerms) {
|
|
73
|
+
addTerm(boldTerm, { artifactType: artifact.type, file: artifact.file });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return termMap;
|
|
77
|
+
}
|
|
78
|
+
function levenshtein(a, b) {
|
|
79
|
+
const m = a.length;
|
|
80
|
+
const n = b.length;
|
|
81
|
+
if (m === 0)
|
|
82
|
+
return n;
|
|
83
|
+
if (n === 0)
|
|
84
|
+
return m;
|
|
85
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
86
|
+
for (let i = 0; i <= m; i++)
|
|
87
|
+
dp[i][0] = i;
|
|
88
|
+
for (let j = 0; j <= n; j++)
|
|
89
|
+
dp[0][j] = j;
|
|
90
|
+
for (let i = 1; i <= m; i++) {
|
|
91
|
+
for (let j = 1; j <= n; j++) {
|
|
92
|
+
dp[i][j] = a[i - 1] === b[j - 1]
|
|
93
|
+
? dp[i - 1][j - 1]
|
|
94
|
+
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return dp[m][n];
|
|
98
|
+
}
|
|
99
|
+
function sharesStem(a, b) {
|
|
100
|
+
const wordsA = a.split(/\s+/).filter((w) => w.length > 3);
|
|
101
|
+
const wordsB = b.split(/\s+/).filter((w) => w.length > 3);
|
|
102
|
+
for (const wa of wordsA) {
|
|
103
|
+
for (const wb of wordsB) {
|
|
104
|
+
const shorter = Math.min(wa.length, wb.length);
|
|
105
|
+
const prefix = Math.min(4, shorter);
|
|
106
|
+
if (wa.slice(0, prefix) === wb.slice(0, prefix) && wa !== wb) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
function checkTermReconciliation(termMap) {
|
|
114
|
+
const issues = [];
|
|
115
|
+
const entries = Array.from(termMap.values()).filter((e) => e.sources.length > 0 && e.normalizedTerm.length > 3);
|
|
116
|
+
for (let i = 0; i < entries.length; i++) {
|
|
117
|
+
for (let j = i + 1; j < entries.length; j++) {
|
|
118
|
+
const a = entries[i];
|
|
119
|
+
const b = entries[j];
|
|
120
|
+
if (a.normalizedTerm === b.normalizedTerm)
|
|
121
|
+
continue;
|
|
122
|
+
const dist = levenshtein(a.normalizedTerm, b.normalizedTerm);
|
|
123
|
+
const maxLen = Math.max(a.normalizedTerm.length, b.normalizedTerm.length);
|
|
124
|
+
const ratio = dist / maxLen;
|
|
125
|
+
const isSimilar = (ratio < 0.25 && maxLen > 5) || sharesStem(a.normalizedTerm, b.normalizedTerm);
|
|
126
|
+
if (isSimilar) {
|
|
127
|
+
const aTypes = [...new Set(a.sources.map((s) => s.artifactType))];
|
|
128
|
+
const bTypes = [...new Set(b.sources.map((s) => s.artifactType))];
|
|
129
|
+
const crossDoc = aTypes.some((t) => !bTypes.includes(t)) || bTypes.some((t) => !aTypes.includes(t));
|
|
130
|
+
if (crossDoc) {
|
|
131
|
+
issues.push({
|
|
132
|
+
level: "warning",
|
|
133
|
+
code: "term_inconsistency",
|
|
134
|
+
check: "terminology",
|
|
135
|
+
message: `Similar terms used across documents: "${a.term}" (${aTypes.join(", ")}) vs "${b.term}" (${bTypes.join(", ")})`,
|
|
136
|
+
suggestion: "Standardize terminology across all artifacts to avoid ambiguity."
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return issues;
|
|
143
|
+
}
|