@shahmarasy/prodo 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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 +467 -0
- package/dist/cli/init-tui.d.ts +23 -0
- package/dist/cli/init-tui.js +183 -0
- package/dist/cli/init.d.ts +12 -0
- package/dist/cli/init.js +335 -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/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 +8 -0
- package/dist/core/settings.js +43 -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/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 +12 -11
- package/dist/providers/openai-provider.d.ts +1 -1
- package/dist/providers/openai-provider.js +13 -13
- package/dist/skill-engine/context.d.ts +7 -0
- package/dist/skill-engine/context.js +76 -0
- package/dist/skill-engine/discovery.d.ts +2 -0
- package/dist/skill-engine/discovery.js +52 -0
- package/dist/skill-engine/graph.d.ts +4 -0
- package/dist/skill-engine/graph.js +114 -0
- package/dist/skill-engine/index.d.ts +11 -0
- package/dist/skill-engine/index.js +49 -0
- package/dist/skill-engine/pipeline.d.ts +9 -0
- package/dist/skill-engine/pipeline.js +84 -0
- package/dist/skill-engine/registry.d.ts +12 -0
- package/dist/skill-engine/registry.js +74 -0
- package/dist/skill-engine/types.d.ts +66 -0
- package/dist/skill-engine/types.js +2 -0
- package/dist/skill-engine/validator.d.ts +4 -0
- package/dist/skill-engine/validator.js +90 -0
- 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/fix.d.ts +2 -0
- package/dist/skills/fix.js +41 -0
- package/dist/skills/generate-artifact-skill.d.ts +2 -0
- package/dist/skills/generate-artifact-skill.js +32 -0
- package/dist/skills/generate-artifact.d.ts +2 -0
- package/dist/skills/generate-artifact.js +42 -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/normalize.d.ts +2 -0
- package/dist/skills/normalize.js +29 -0
- package/dist/skills/register-core.d.ts +2 -0
- package/dist/skills/register-core.js +21 -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/skills/validate.d.ts +2 -0
- package/dist/skills/validate.js +37 -0
- package/package.json +72 -45
- 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 -58
- package/src/{doctor.ts → cli/doctor.ts} +157 -137
- package/src/cli/fix-tui.ts +111 -0
- package/src/{cli.ts → cli/index.ts} +463 -410
- package/src/{init-tui.ts → cli/init-tui.ts} +49 -37
- package/src/{init.ts → cli/init.ts} +399 -398
- 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 -1072
- 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/{normalize.ts → core/normalize.ts} +145 -137
- 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} +8 -2
- 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 -452
- 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 -64
- package/src/{validate.ts → core/validate.ts} +252 -246
- package/src/{validator.ts → core/validator.ts} +92 -92
- package/src/{version.ts → core/version.ts} +24 -24
- package/src/{workflow-commands.ts → core/workflow-commands.ts} +32 -32
- 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 -88
- package/src/skill-engine/context.ts +90 -0
- package/src/skill-engine/discovery.ts +57 -0
- package/src/skill-engine/graph.ts +136 -0
- package/src/skill-engine/index.ts +55 -0
- package/src/skill-engine/pipeline.ts +112 -0
- package/src/skill-engine/registry.ts +75 -0
- package/src/skill-engine/types.ts +81 -0
- package/src/skill-engine/validator.ts +135 -0
- package/src/skills/fix.ts +45 -0
- package/src/skills/generate-artifact.ts +48 -0
- package/src/skills/normalize.ts +32 -0
- package/src/skills/register-core.ts +27 -0
- package/src/skills/validate.ts +40 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type CleanOptions = {
|
|
2
|
+
cwd: string;
|
|
3
|
+
dryRun?: boolean;
|
|
4
|
+
log?: (message: string) => void;
|
|
5
|
+
};
|
|
6
|
+
export type CleanResult = {
|
|
7
|
+
removedPaths: string[];
|
|
8
|
+
preservedPaths: string[];
|
|
9
|
+
};
|
|
10
|
+
export declare function runClean(options: CleanOptions): Promise<CleanResult>;
|
|
@@ -0,0 +1,74 @@
|
|
|
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.runClean = runClean;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const paths_1 = require("./paths");
|
|
10
|
+
const utils_1 = require("./utils");
|
|
11
|
+
const REMOVABLE_DIRS = [
|
|
12
|
+
"product-docs"
|
|
13
|
+
];
|
|
14
|
+
const REMOVABLE_PRODO_SUBDIRS = [
|
|
15
|
+
"state",
|
|
16
|
+
"briefs"
|
|
17
|
+
];
|
|
18
|
+
const PRESERVED_PRODO_FILES = [
|
|
19
|
+
"settings.json",
|
|
20
|
+
"hooks.yml",
|
|
21
|
+
"config.json"
|
|
22
|
+
];
|
|
23
|
+
async function runClean(options) {
|
|
24
|
+
const { cwd, dryRun = false, log = console.log } = options;
|
|
25
|
+
const result = { removedPaths: [], preservedPaths: [] };
|
|
26
|
+
for (const dir of REMOVABLE_DIRS) {
|
|
27
|
+
const fullPath = node_path_1.default.join(cwd, dir);
|
|
28
|
+
if (await (0, utils_1.fileExists)(fullPath)) {
|
|
29
|
+
if (dryRun) {
|
|
30
|
+
log(`[Dry Run] Would remove: ${fullPath}`);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
await promises_1.default.rm(fullPath, { recursive: true, force: true });
|
|
34
|
+
log(`Removed: ${fullPath}`);
|
|
35
|
+
}
|
|
36
|
+
result.removedPaths.push(fullPath);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const prodo = (0, paths_1.prodoPath)(cwd);
|
|
40
|
+
for (const subdir of REMOVABLE_PRODO_SUBDIRS) {
|
|
41
|
+
const fullPath = node_path_1.default.join(prodo, subdir);
|
|
42
|
+
if (await (0, utils_1.fileExists)(fullPath)) {
|
|
43
|
+
if (dryRun) {
|
|
44
|
+
log(`[Dry Run] Would remove: ${fullPath}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
await promises_1.default.rm(fullPath, { recursive: true, force: true });
|
|
48
|
+
log(`Removed: ${fullPath}`);
|
|
49
|
+
}
|
|
50
|
+
result.removedPaths.push(fullPath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const briefPath = node_path_1.default.join(cwd, "brief.md");
|
|
54
|
+
if (await (0, utils_1.fileExists)(briefPath)) {
|
|
55
|
+
result.preservedPaths.push(briefPath);
|
|
56
|
+
}
|
|
57
|
+
for (const file of PRESERVED_PRODO_FILES) {
|
|
58
|
+
const fullPath = node_path_1.default.join(prodo, file);
|
|
59
|
+
if (await (0, utils_1.fileExists)(fullPath)) {
|
|
60
|
+
result.preservedPaths.push(fullPath);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const preservedDirs = ["templates", "schemas", "prompts", "commands", "presets"];
|
|
64
|
+
for (const dir of preservedDirs) {
|
|
65
|
+
const fullPath = node_path_1.default.join(prodo, dir);
|
|
66
|
+
if (await (0, utils_1.fileExists)(fullPath)) {
|
|
67
|
+
result.preservedPaths.push(fullPath);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (result.preservedPaths.length > 0 && !dryRun) {
|
|
71
|
+
log(`Preserved: ${result.preservedPaths.length} file(s)/dir(s)`);
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ArtifactDoc, ArtifactType, ValidationIssue } from "./types";
|
|
2
|
+
type LoadedArtifact = {
|
|
3
|
+
type: ArtifactType;
|
|
4
|
+
file: string;
|
|
5
|
+
doc: ArtifactDoc;
|
|
6
|
+
};
|
|
7
|
+
export declare function checkConsistency(cwd: string, loadedArtifacts: LoadedArtifact[], normalizedBrief: Record<string, unknown>): Promise<ValidationIssue[]>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,328 @@
|
|
|
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.checkConsistency = checkConsistency;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const artifact_registry_1 = require("./artifact-registry");
|
|
9
|
+
const markdown_1 = require("./markdown");
|
|
10
|
+
const normalized_brief_1 = require("./normalized-brief");
|
|
11
|
+
const providers_1 = require("../providers");
|
|
12
|
+
const terminology_1 = require("./terminology");
|
|
13
|
+
const tracing_1 = require("./tracing");
|
|
14
|
+
function asStringArray(value) {
|
|
15
|
+
if (!Array.isArray(value))
|
|
16
|
+
return [];
|
|
17
|
+
return value.filter((item) => typeof item === "string");
|
|
18
|
+
}
|
|
19
|
+
function readCoverage(frontmatter) {
|
|
20
|
+
const coverage = frontmatter.contract_coverage;
|
|
21
|
+
return {
|
|
22
|
+
goals: asStringArray(coverage?.goals),
|
|
23
|
+
core_features: asStringArray(coverage?.core_features),
|
|
24
|
+
constraints: asStringArray(coverage?.constraints)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function checkMissingArtifacts(cwd, loadedArtifacts) {
|
|
28
|
+
const expectedTypes = await (0, artifact_registry_1.listArtifactTypes)(cwd);
|
|
29
|
+
const present = new Set(loadedArtifacts.map((item) => item.type));
|
|
30
|
+
const missing = expectedTypes.filter((type) => !present.has(type));
|
|
31
|
+
if (missing.length === 0)
|
|
32
|
+
return [];
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
level: "warning",
|
|
36
|
+
code: "missing_artifacts",
|
|
37
|
+
check: "schema",
|
|
38
|
+
message: `Some artifacts are missing from outputs: ${missing.join(", ")}`,
|
|
39
|
+
suggestion: "Run the corresponding prodo-* commands before final validation."
|
|
40
|
+
}
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
async function checkContractCoverage(cwd, loaded, normalizedBrief) {
|
|
44
|
+
const issues = [];
|
|
45
|
+
const normalized = (0, normalized_brief_1.parseNormalizedBriefOrThrow)(normalizedBrief);
|
|
46
|
+
const expected = (0, normalized_brief_1.contractIds)(normalized.contracts);
|
|
47
|
+
for (const artifact of loaded) {
|
|
48
|
+
const def = await (0, artifact_registry_1.getArtifactDefinition)(cwd, artifact.type);
|
|
49
|
+
const coverage = readCoverage(artifact.doc.frontmatter);
|
|
50
|
+
for (const key of def.required_contracts) {
|
|
51
|
+
const missing = expected[key].filter((id) => !coverage[key].includes(id));
|
|
52
|
+
if (missing.length === 0)
|
|
53
|
+
continue;
|
|
54
|
+
issues.push({
|
|
55
|
+
level: "error",
|
|
56
|
+
code: "missing_contract_coverage",
|
|
57
|
+
check: "tag_coverage",
|
|
58
|
+
artifactType: artifact.type,
|
|
59
|
+
file: artifact.file,
|
|
60
|
+
field: `frontmatter.contract_coverage.${key}`,
|
|
61
|
+
message: `Artifact is missing required contract IDs for ${key}: ${missing.join(", ")}`,
|
|
62
|
+
suggestion: "Regenerate artifact and include explicit contract tags such as [G1], [F2], [C1]."
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return issues;
|
|
67
|
+
}
|
|
68
|
+
function checkUpstreamReferences(loaded) {
|
|
69
|
+
const issues = [];
|
|
70
|
+
const filesByName = new Set(loaded.map((item) => node_path_1.default.normalize(item.file)));
|
|
71
|
+
for (const artifact of loaded) {
|
|
72
|
+
const upstream = artifact.doc.frontmatter.upstream_artifacts;
|
|
73
|
+
if (!Array.isArray(upstream))
|
|
74
|
+
continue;
|
|
75
|
+
for (const rawItem of upstream) {
|
|
76
|
+
if (typeof rawItem !== "string")
|
|
77
|
+
continue;
|
|
78
|
+
const resolved = node_path_1.default.normalize(node_path_1.default.resolve(node_path_1.default.dirname(artifact.file), rawItem));
|
|
79
|
+
if (!filesByName.has(resolved)) {
|
|
80
|
+
issues.push({
|
|
81
|
+
level: "error",
|
|
82
|
+
code: "broken_upstream_reference",
|
|
83
|
+
check: "schema",
|
|
84
|
+
artifactType: artifact.type,
|
|
85
|
+
file: artifact.file,
|
|
86
|
+
field: "frontmatter.upstream_artifacts",
|
|
87
|
+
message: `Referenced upstream artifact not found: ${rawItem}`,
|
|
88
|
+
suggestion: "Regenerate this artifact or update upstream_artifacts paths to existing outputs."
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return issues;
|
|
94
|
+
}
|
|
95
|
+
// taggedLinesByContract is now imported from ./markdown
|
|
96
|
+
function parseJsonObject(raw, fallback) {
|
|
97
|
+
const trimmed = raw.trim();
|
|
98
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
99
|
+
const candidate = fenced ? fenced[1] : trimmed;
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(candidate);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return fallback;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function hasEnglishLeak(body) {
|
|
108
|
+
const markers = [" the ", " and ", " with ", " user ", " should ", " must "];
|
|
109
|
+
const normalized = ` ${body.toLowerCase().replace(/\s+/g, " ")} `;
|
|
110
|
+
return markers.filter((item) => normalized.includes(item)).length >= 2;
|
|
111
|
+
}
|
|
112
|
+
function checkLanguageConsistency(loaded) {
|
|
113
|
+
const issues = [];
|
|
114
|
+
const languages = new Set();
|
|
115
|
+
for (const artifact of loaded) {
|
|
116
|
+
const lang = String((artifact.doc.frontmatter.language ?? "")).toLowerCase();
|
|
117
|
+
if (lang)
|
|
118
|
+
languages.add(lang);
|
|
119
|
+
if (lang.startsWith("tr") && hasEnglishLeak(artifact.doc.body)) {
|
|
120
|
+
issues.push({
|
|
121
|
+
level: "error",
|
|
122
|
+
code: "language_mixed_content",
|
|
123
|
+
check: "schema",
|
|
124
|
+
artifactType: artifact.type,
|
|
125
|
+
file: artifact.file,
|
|
126
|
+
message: "Artifact contains mixed language content while target language is Turkish.",
|
|
127
|
+
suggestion: "Regenerate artifact with strict Turkish output."
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (languages.size > 1) {
|
|
132
|
+
issues.push({
|
|
133
|
+
level: "error",
|
|
134
|
+
code: "language_inconsistent_across_artifacts",
|
|
135
|
+
check: "schema",
|
|
136
|
+
message: "Artifacts have inconsistent language settings.",
|
|
137
|
+
suggestion: "Regenerate artifacts so all frontmatter.language values match."
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return issues;
|
|
141
|
+
}
|
|
142
|
+
async function checkContractRelevance(loaded, normalizedBrief) {
|
|
143
|
+
const normalized = (0, normalized_brief_1.parseNormalizedBriefOrThrow)(normalizedBrief);
|
|
144
|
+
const contractMap = new Map();
|
|
145
|
+
for (const item of normalized.contracts.goals)
|
|
146
|
+
contractMap.set(item.id, item.text);
|
|
147
|
+
for (const item of normalized.contracts.core_features)
|
|
148
|
+
contractMap.set(item.id, item.text);
|
|
149
|
+
for (const item of normalized.contracts.constraints)
|
|
150
|
+
contractMap.set(item.id, item.text);
|
|
151
|
+
const provider = (0, providers_1.createProvider)();
|
|
152
|
+
const issues = [];
|
|
153
|
+
for (const artifact of loaded) {
|
|
154
|
+
const taggedLines = (0, markdown_1.taggedLinesByContract)(artifact.doc.body);
|
|
155
|
+
for (const tagged of taggedLines) {
|
|
156
|
+
const contractText = contractMap.get(tagged.contractId);
|
|
157
|
+
if (!contractText) {
|
|
158
|
+
issues.push({
|
|
159
|
+
level: "error",
|
|
160
|
+
code: "unknown_contract_tag",
|
|
161
|
+
check: "contract_relevance",
|
|
162
|
+
artifactType: artifact.type,
|
|
163
|
+
file: artifact.file,
|
|
164
|
+
field: tagged.contractId,
|
|
165
|
+
message: `Unknown contract tag used: ${tagged.contractId}`,
|
|
166
|
+
suggestion: "Use only contract IDs that exist in normalized brief contracts."
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const response = await provider.generate("Evaluate if tagged line semantically matches contract text.", {
|
|
171
|
+
contract_id: tagged.contractId,
|
|
172
|
+
contract_text: contractText,
|
|
173
|
+
context_text: tagged.line
|
|
174
|
+
}, {
|
|
175
|
+
artifactType: "contract_relevance",
|
|
176
|
+
requiredHeadings: [],
|
|
177
|
+
requiredContracts: []
|
|
178
|
+
});
|
|
179
|
+
const verdict = parseJsonObject(response.body, {});
|
|
180
|
+
const relevant = Boolean(verdict.relevant);
|
|
181
|
+
if (!relevant) {
|
|
182
|
+
issues.push({
|
|
183
|
+
level: "error",
|
|
184
|
+
code: "irrelevant_contract_tag_usage",
|
|
185
|
+
check: "contract_relevance",
|
|
186
|
+
artifactType: artifact.type,
|
|
187
|
+
file: artifact.file,
|
|
188
|
+
field: tagged.contractId,
|
|
189
|
+
message: `Tag ${tagged.contractId} does not match nearby content semantically.`,
|
|
190
|
+
suggestion: verdict.reason ?? "Rewrite the tagged sentence so it clearly addresses the referenced contract."
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return issues;
|
|
196
|
+
}
|
|
197
|
+
async function checkSemanticPairs(loaded) {
|
|
198
|
+
const byType = new Map();
|
|
199
|
+
for (const artifact of loaded)
|
|
200
|
+
byType.set(artifact.type, artifact);
|
|
201
|
+
const pairs = [
|
|
202
|
+
["prd", "stories"],
|
|
203
|
+
["workflow", "techspec"],
|
|
204
|
+
["workflow", "wireframe"]
|
|
205
|
+
];
|
|
206
|
+
const provider = (0, providers_1.createProvider)();
|
|
207
|
+
const issues = [];
|
|
208
|
+
for (const [leftType, rightType] of pairs) {
|
|
209
|
+
const left = byType.get(leftType);
|
|
210
|
+
const right = byType.get(rightType);
|
|
211
|
+
if (!left || !right)
|
|
212
|
+
continue;
|
|
213
|
+
const result = await provider.generate("Compare paired artifacts semantically and return contradictions.", {
|
|
214
|
+
pair: {
|
|
215
|
+
left_type: leftType,
|
|
216
|
+
left_file: left.file,
|
|
217
|
+
left_coverage: readCoverage(left.doc.frontmatter),
|
|
218
|
+
left_body: left.doc.body,
|
|
219
|
+
right_type: rightType,
|
|
220
|
+
right_file: right.file,
|
|
221
|
+
right_coverage: readCoverage(right.doc.frontmatter),
|
|
222
|
+
right_body: right.doc.body
|
|
223
|
+
}
|
|
224
|
+
}, {
|
|
225
|
+
artifactType: "semantic_consistency",
|
|
226
|
+
requiredHeadings: [],
|
|
227
|
+
requiredContracts: []
|
|
228
|
+
});
|
|
229
|
+
const parsed = parseJsonObject(result.body, { issues: [] });
|
|
230
|
+
for (const item of parsed.issues ?? []) {
|
|
231
|
+
issues.push({
|
|
232
|
+
level: (item.level === "warning" ? "warning" : "error"),
|
|
233
|
+
code: typeof item.code === "string" ? item.code : "semantic_inconsistency",
|
|
234
|
+
check: "semantic_consistency",
|
|
235
|
+
file: typeof item.file === "string" ? item.file : left.file,
|
|
236
|
+
field: typeof item.contract_id === "string" ? item.contract_id : undefined,
|
|
237
|
+
message: typeof item.message === "string"
|
|
238
|
+
? item.message
|
|
239
|
+
: `Semantic mismatch between ${leftType} and ${rightType}.`,
|
|
240
|
+
suggestion: typeof item.suggestion === "string"
|
|
241
|
+
? item.suggestion
|
|
242
|
+
: `Align ${leftType} and ${rightType} decisions and regenerate.`
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return issues;
|
|
247
|
+
}
|
|
248
|
+
function checkCrossReferences(loadedArtifacts) {
|
|
249
|
+
const issues = [];
|
|
250
|
+
const artifactTypeNames = new Set(loadedArtifacts.map((a) => a.type));
|
|
251
|
+
const sectionsByType = new Map();
|
|
252
|
+
for (const artifact of loadedArtifacts) {
|
|
253
|
+
const sections = (0, markdown_1.parseMarkdownSections)(artifact.doc.body);
|
|
254
|
+
const headingSet = new Set(sections.map((s) => s.headingKey));
|
|
255
|
+
sectionsByType.set(artifact.type, headingSet);
|
|
256
|
+
}
|
|
257
|
+
const crossRefPattern = /(?:see|refer to|as (?:defined|described|specified) in)\s+(prd|workflow|wireframe|stories|techspec)(?:\s+(?:section\s+)?[""]?([^"".,)\n]+)[""]?)?/gi;
|
|
258
|
+
for (const artifact of loadedArtifacts) {
|
|
259
|
+
const matches = artifact.doc.body.matchAll(crossRefPattern);
|
|
260
|
+
for (const match of matches) {
|
|
261
|
+
const refType = match[1].toLowerCase();
|
|
262
|
+
const refSection = match[2]?.trim();
|
|
263
|
+
if (!artifactTypeNames.has(refType)) {
|
|
264
|
+
issues.push({
|
|
265
|
+
level: "warning",
|
|
266
|
+
code: "broken_cross_reference",
|
|
267
|
+
check: "cross_reference",
|
|
268
|
+
artifactType: artifact.type,
|
|
269
|
+
file: artifact.file,
|
|
270
|
+
message: `Cross-reference to "${refType}" but that artifact does not exist.`,
|
|
271
|
+
suggestion: `Generate the ${refType} artifact or remove the cross-reference.`
|
|
272
|
+
});
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (refSection) {
|
|
276
|
+
const targetSections = sectionsByType.get(refType);
|
|
277
|
+
if (targetSections) {
|
|
278
|
+
const normalizedRef = refSection.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
279
|
+
const found = [...targetSections].some((s) => s.includes(normalizedRef) || normalizedRef.includes(s));
|
|
280
|
+
if (!found) {
|
|
281
|
+
issues.push({
|
|
282
|
+
level: "warning",
|
|
283
|
+
code: "broken_cross_reference",
|
|
284
|
+
check: "cross_reference",
|
|
285
|
+
artifactType: artifact.type,
|
|
286
|
+
file: artifact.file,
|
|
287
|
+
message: `Cross-reference to "${refType} section ${refSection}" but that section was not found.`,
|
|
288
|
+
suggestion: `Verify the section name or update the cross-reference.`
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return issues;
|
|
296
|
+
}
|
|
297
|
+
async function checkConsistency(cwd, loadedArtifacts, normalizedBrief) {
|
|
298
|
+
const baseIssues = [
|
|
299
|
+
...(await checkMissingArtifacts(cwd, loadedArtifacts)),
|
|
300
|
+
...(await checkContractCoverage(cwd, loadedArtifacts, normalizedBrief)),
|
|
301
|
+
...checkUpstreamReferences(loadedArtifacts),
|
|
302
|
+
...checkLanguageConsistency(loadedArtifacts)
|
|
303
|
+
];
|
|
304
|
+
const relevanceIssues = await checkContractRelevance(loadedArtifacts, normalizedBrief);
|
|
305
|
+
const semanticIssues = await checkSemanticPairs(loadedArtifacts);
|
|
306
|
+
const crossRefIssues = checkCrossReferences(loadedArtifacts);
|
|
307
|
+
let terminologyIssues = [];
|
|
308
|
+
let tracingIssues = [];
|
|
309
|
+
try {
|
|
310
|
+
const parsed = (0, normalized_brief_1.parseNormalizedBriefOrThrow)(normalizedBrief);
|
|
311
|
+
const termMap = (0, terminology_1.buildTermMap)(parsed, loadedArtifacts);
|
|
312
|
+
terminologyIssues = (0, terminology_1.checkTermReconciliation)(termMap);
|
|
313
|
+
const traceMap = (0, tracing_1.buildTraceMap)(parsed, loadedArtifacts);
|
|
314
|
+
const presentTypes = loadedArtifacts.map((a) => a.type);
|
|
315
|
+
tracingIssues = (0, tracing_1.checkRequirementCompleteness)(traceMap, parsed, presentTypes);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// normalized brief parse failed — skip terminology/tracing (other checks will catch it)
|
|
319
|
+
}
|
|
320
|
+
return [
|
|
321
|
+
...baseIssues,
|
|
322
|
+
...relevanceIssues,
|
|
323
|
+
...semanticIssues,
|
|
324
|
+
...crossRefIssues,
|
|
325
|
+
...terminologyIssues,
|
|
326
|
+
...tracingIssues
|
|
327
|
+
];
|
|
328
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ContractCoverage } from "./types";
|
|
2
|
+
export declare const PRODO_DIR = ".prodo";
|
|
3
|
+
export declare const DEFAULT_STATUS = "draft";
|
|
4
|
+
export declare function defaultOutputDir(artifactType: string): string;
|
|
5
|
+
export declare function defaultRequiredHeadings(artifactType: string): string[];
|
|
6
|
+
export declare function defaultUpstreamByArtifact(artifactType: string): string[];
|
|
7
|
+
export declare function defaultRequiredContractsByArtifact(artifactType: string): Array<keyof ContractCoverage>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_STATUS = exports.PRODO_DIR = void 0;
|
|
4
|
+
exports.defaultOutputDir = defaultOutputDir;
|
|
5
|
+
exports.defaultRequiredHeadings = defaultRequiredHeadings;
|
|
6
|
+
exports.defaultUpstreamByArtifact = defaultUpstreamByArtifact;
|
|
7
|
+
exports.defaultRequiredContractsByArtifact = defaultRequiredContractsByArtifact;
|
|
8
|
+
exports.PRODO_DIR = ".prodo";
|
|
9
|
+
exports.DEFAULT_STATUS = "draft";
|
|
10
|
+
const CORE_OUTPUT_DIR_BY_ARTIFACT = {
|
|
11
|
+
prd: "prd",
|
|
12
|
+
workflow: "workflows",
|
|
13
|
+
wireframe: "wireframes",
|
|
14
|
+
stories: "stories",
|
|
15
|
+
techspec: "techspec"
|
|
16
|
+
};
|
|
17
|
+
const CORE_REQUIRED_HEADINGS = {
|
|
18
|
+
prd: ["## Problem", "## Goals", "## Scope", "## Requirements"],
|
|
19
|
+
workflow: [
|
|
20
|
+
"## Flow Purpose",
|
|
21
|
+
"## Actors",
|
|
22
|
+
"## Preconditions",
|
|
23
|
+
"## Main Flow",
|
|
24
|
+
"## Edge Cases",
|
|
25
|
+
"## Postconditions"
|
|
26
|
+
],
|
|
27
|
+
wireframe: [
|
|
28
|
+
"## Screen Purpose",
|
|
29
|
+
"## Primary Actor",
|
|
30
|
+
"## Main Sections",
|
|
31
|
+
"## Fields/Inputs",
|
|
32
|
+
"## Actions/Buttons",
|
|
33
|
+
"## States/Messages",
|
|
34
|
+
"## Notes"
|
|
35
|
+
],
|
|
36
|
+
stories: ["## User Stories", "## Acceptance Criteria"],
|
|
37
|
+
techspec: ["## Architecture", "## Data Model", "## APIs", "## Risks"]
|
|
38
|
+
};
|
|
39
|
+
const CORE_UPSTREAM_BY_ARTIFACT = {
|
|
40
|
+
prd: [],
|
|
41
|
+
workflow: ["prd"],
|
|
42
|
+
wireframe: ["prd", "workflow"],
|
|
43
|
+
stories: ["prd"],
|
|
44
|
+
techspec: ["prd", "stories"]
|
|
45
|
+
};
|
|
46
|
+
const CORE_REQUIRED_CONTRACTS_BY_ARTIFACT = {
|
|
47
|
+
prd: ["goals", "core_features"],
|
|
48
|
+
workflow: ["core_features"],
|
|
49
|
+
wireframe: ["core_features"],
|
|
50
|
+
stories: ["core_features"],
|
|
51
|
+
techspec: ["core_features", "constraints"]
|
|
52
|
+
};
|
|
53
|
+
function defaultOutputDir(artifactType) {
|
|
54
|
+
return CORE_OUTPUT_DIR_BY_ARTIFACT[artifactType] ?? `${artifactType}s`;
|
|
55
|
+
}
|
|
56
|
+
function defaultRequiredHeadings(artifactType) {
|
|
57
|
+
return CORE_REQUIRED_HEADINGS[artifactType] ?? ["## Summary", "## Details"];
|
|
58
|
+
}
|
|
59
|
+
function defaultUpstreamByArtifact(artifactType) {
|
|
60
|
+
return CORE_UPSTREAM_BY_ARTIFACT[artifactType] ?? [];
|
|
61
|
+
}
|
|
62
|
+
function defaultRequiredContractsByArtifact(artifactType) {
|
|
63
|
+
return CORE_REQUIRED_CONTRACTS_BY_ARTIFACT[artifactType] ?? ["core_features"];
|
|
64
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ArtifactType, ValidationIssue } from "./types";
|
|
2
|
+
import { type ValidateResult } from "./validate";
|
|
3
|
+
export type FixProposal = {
|
|
4
|
+
targets: ArtifactType[];
|
|
5
|
+
issues: ValidationIssue[];
|
|
6
|
+
issuesByArtifact: Map<ArtifactType, ValidationIssue[]>;
|
|
7
|
+
initialReport: ValidateResult;
|
|
8
|
+
};
|
|
9
|
+
export type FixOptions = {
|
|
10
|
+
cwd: string;
|
|
11
|
+
agent?: string;
|
|
12
|
+
strict?: boolean;
|
|
13
|
+
report?: string;
|
|
14
|
+
dryRun?: boolean;
|
|
15
|
+
log?: (message: string) => void;
|
|
16
|
+
};
|
|
17
|
+
export type FixResult = {
|
|
18
|
+
proposal: FixProposal;
|
|
19
|
+
applied: boolean;
|
|
20
|
+
finalPass: boolean;
|
|
21
|
+
reportPath: string;
|
|
22
|
+
backupDir?: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function resolveFixTargets(cwd: string, artifactTypes: ArtifactType[], issues: Array<{
|
|
25
|
+
artifactType?: ArtifactType;
|
|
26
|
+
}>): Promise<ArtifactType[]>;
|
|
27
|
+
export declare function buildFixProposal(options: FixOptions): Promise<FixProposal>;
|
|
28
|
+
export declare function createBackup(cwd: string, targets: ArtifactType[]): Promise<string>;
|
|
29
|
+
export declare function restoreBackup(cwd: string, backupDir: string): Promise<void>;
|
|
30
|
+
export declare function applyFix(cwd: string, proposal: FixProposal, options: FixOptions): Promise<FixResult>;
|
|
31
|
+
export declare function runFix(options: FixOptions): Promise<FixResult>;
|