@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
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
import type { ContractCoverage, CoreArtifactType } from "./types";
|
|
2
|
-
|
|
3
|
-
export const PRODO_DIR = ".prodo";
|
|
4
|
-
export const DEFAULT_STATUS = "draft";
|
|
5
|
-
|
|
6
|
-
const CORE_OUTPUT_DIR_BY_ARTIFACT: Record<CoreArtifactType, string> = {
|
|
7
|
-
prd: "prd",
|
|
8
|
-
workflow: "workflows",
|
|
9
|
-
wireframe: "wireframes",
|
|
10
|
-
stories: "stories",
|
|
11
|
-
techspec: "techspec"
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const CORE_REQUIRED_HEADINGS: Record<CoreArtifactType, string[]> = {
|
|
15
|
-
prd: ["## Problem", "## Goals", "## Scope", "## Requirements"],
|
|
16
|
-
workflow: [
|
|
17
|
-
"## Flow Purpose",
|
|
18
|
-
"## Actors",
|
|
19
|
-
"## Preconditions",
|
|
20
|
-
"## Main Flow",
|
|
21
|
-
"## Edge Cases",
|
|
22
|
-
"## Postconditions"
|
|
23
|
-
],
|
|
24
|
-
wireframe: [
|
|
25
|
-
"## Screen Purpose",
|
|
26
|
-
"## Primary Actor",
|
|
27
|
-
"## Main Sections",
|
|
28
|
-
"## Fields/Inputs",
|
|
29
|
-
"## Actions/Buttons",
|
|
30
|
-
"## States/Messages",
|
|
31
|
-
"## Notes"
|
|
32
|
-
],
|
|
33
|
-
stories: ["## User Stories", "## Acceptance Criteria"],
|
|
34
|
-
techspec: ["## Architecture", "## Data Model", "## APIs", "## Risks"]
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const CORE_UPSTREAM_BY_ARTIFACT: Record<CoreArtifactType, CoreArtifactType[]> = {
|
|
38
|
-
prd: [],
|
|
39
|
-
workflow: ["prd"],
|
|
40
|
-
wireframe: ["prd", "workflow"],
|
|
41
|
-
stories: ["prd"],
|
|
42
|
-
techspec: ["prd", "stories"]
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const CORE_REQUIRED_CONTRACTS_BY_ARTIFACT: Record<
|
|
46
|
-
CoreArtifactType,
|
|
47
|
-
Array<keyof ContractCoverage>
|
|
48
|
-
> = {
|
|
49
|
-
prd: ["goals", "core_features"],
|
|
50
|
-
workflow: ["core_features"],
|
|
51
|
-
wireframe: ["core_features"],
|
|
52
|
-
stories: ["core_features"],
|
|
53
|
-
techspec: ["core_features", "constraints"]
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export function defaultOutputDir(artifactType: string): string {
|
|
57
|
-
return CORE_OUTPUT_DIR_BY_ARTIFACT[artifactType as CoreArtifactType] ?? `${artifactType}s`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function defaultRequiredHeadings(artifactType: string): string[] {
|
|
61
|
-
return CORE_REQUIRED_HEADINGS[artifactType as CoreArtifactType] ?? ["## Summary", "## Details"];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function defaultUpstreamByArtifact(artifactType: string): string[] {
|
|
65
|
-
return CORE_UPSTREAM_BY_ARTIFACT[artifactType as CoreArtifactType] ?? [];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function defaultRequiredContractsByArtifact(
|
|
69
|
-
artifactType: string
|
|
70
|
-
): Array<keyof ContractCoverage> {
|
|
71
|
-
return CORE_REQUIRED_CONTRACTS_BY_ARTIFACT[artifactType as CoreArtifactType] ?? ["core_features"];
|
|
72
|
-
}
|
|
1
|
+
import type { ContractCoverage, CoreArtifactType } from "./types";
|
|
2
|
+
|
|
3
|
+
export const PRODO_DIR = ".prodo";
|
|
4
|
+
export const DEFAULT_STATUS = "draft";
|
|
5
|
+
|
|
6
|
+
const CORE_OUTPUT_DIR_BY_ARTIFACT: Record<CoreArtifactType, string> = {
|
|
7
|
+
prd: "prd",
|
|
8
|
+
workflow: "workflows",
|
|
9
|
+
wireframe: "wireframes",
|
|
10
|
+
stories: "stories",
|
|
11
|
+
techspec: "techspec"
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const CORE_REQUIRED_HEADINGS: Record<CoreArtifactType, string[]> = {
|
|
15
|
+
prd: ["## Problem", "## Goals", "## Scope", "## Requirements"],
|
|
16
|
+
workflow: [
|
|
17
|
+
"## Flow Purpose",
|
|
18
|
+
"## Actors",
|
|
19
|
+
"## Preconditions",
|
|
20
|
+
"## Main Flow",
|
|
21
|
+
"## Edge Cases",
|
|
22
|
+
"## Postconditions"
|
|
23
|
+
],
|
|
24
|
+
wireframe: [
|
|
25
|
+
"## Screen Purpose",
|
|
26
|
+
"## Primary Actor",
|
|
27
|
+
"## Main Sections",
|
|
28
|
+
"## Fields/Inputs",
|
|
29
|
+
"## Actions/Buttons",
|
|
30
|
+
"## States/Messages",
|
|
31
|
+
"## Notes"
|
|
32
|
+
],
|
|
33
|
+
stories: ["## User Stories", "## Acceptance Criteria"],
|
|
34
|
+
techspec: ["## Architecture", "## Data Model", "## APIs", "## Risks"]
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const CORE_UPSTREAM_BY_ARTIFACT: Record<CoreArtifactType, CoreArtifactType[]> = {
|
|
38
|
+
prd: [],
|
|
39
|
+
workflow: ["prd"],
|
|
40
|
+
wireframe: ["prd", "workflow"],
|
|
41
|
+
stories: ["prd"],
|
|
42
|
+
techspec: ["prd", "stories"]
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const CORE_REQUIRED_CONTRACTS_BY_ARTIFACT: Record<
|
|
46
|
+
CoreArtifactType,
|
|
47
|
+
Array<keyof ContractCoverage>
|
|
48
|
+
> = {
|
|
49
|
+
prd: ["goals", "core_features"],
|
|
50
|
+
workflow: ["core_features"],
|
|
51
|
+
wireframe: ["core_features"],
|
|
52
|
+
stories: ["core_features"],
|
|
53
|
+
techspec: ["core_features", "constraints"]
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export function defaultOutputDir(artifactType: string): string {
|
|
57
|
+
return CORE_OUTPUT_DIR_BY_ARTIFACT[artifactType as CoreArtifactType] ?? `${artifactType}s`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function defaultRequiredHeadings(artifactType: string): string[] {
|
|
61
|
+
return CORE_REQUIRED_HEADINGS[artifactType as CoreArtifactType] ?? ["## Summary", "## Details"];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function defaultUpstreamByArtifact(artifactType: string): string[] {
|
|
65
|
+
return CORE_UPSTREAM_BY_ARTIFACT[artifactType as CoreArtifactType] ?? [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function defaultRequiredContractsByArtifact(
|
|
69
|
+
artifactType: string
|
|
70
|
+
): Array<keyof ContractCoverage> {
|
|
71
|
+
return CORE_REQUIRED_CONTRACTS_BY_ARTIFACT[artifactType as CoreArtifactType] ?? ["core_features"];
|
|
72
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export class UserError extends Error {
|
|
2
|
-
constructor(message: string) {
|
|
3
|
-
super(message);
|
|
4
|
-
this.name = "UserError";
|
|
5
|
-
}
|
|
6
|
-
}
|
|
7
|
-
|
|
1
|
+
export class UserError extends Error {
|
|
2
|
+
constructor(message: string) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "UserError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
package/src/core/fix.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { listArtifactDefinitions, listArtifactTypes } from "./artifact-registry";
|
|
4
|
+
import { generateArtifact } from "./artifacts";
|
|
5
|
+
import { UserError } from "./errors";
|
|
6
|
+
import { runHookPhase } from "./hook-executor";
|
|
7
|
+
import { runNormalize } from "./normalize";
|
|
8
|
+
import { getActiveArtifactPath } from "./output-index";
|
|
9
|
+
import { prodoPath } from "./paths";
|
|
10
|
+
import type { ArtifactType, ValidationIssue } from "./types";
|
|
11
|
+
import { ensureDir, fileExists, timestampSlug } from "./utils";
|
|
12
|
+
import { runValidate, type ValidateResult } from "./validate";
|
|
13
|
+
|
|
14
|
+
export type FixProposal = {
|
|
15
|
+
targets: ArtifactType[];
|
|
16
|
+
issues: ValidationIssue[];
|
|
17
|
+
issuesByArtifact: Map<ArtifactType, ValidationIssue[]>;
|
|
18
|
+
initialReport: ValidateResult;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type FixOptions = {
|
|
22
|
+
cwd: string;
|
|
23
|
+
agent?: string;
|
|
24
|
+
strict?: boolean;
|
|
25
|
+
report?: string;
|
|
26
|
+
dryRun?: boolean;
|
|
27
|
+
log?: (message: string) => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type FixResult = {
|
|
31
|
+
proposal: FixProposal;
|
|
32
|
+
applied: boolean;
|
|
33
|
+
finalPass: boolean;
|
|
34
|
+
reportPath: string;
|
|
35
|
+
backupDir?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export async function resolveFixTargets(
|
|
39
|
+
cwd: string,
|
|
40
|
+
artifactTypes: ArtifactType[],
|
|
41
|
+
issues: Array<{ artifactType?: ArtifactType }>
|
|
42
|
+
): Promise<ArtifactType[]> {
|
|
43
|
+
const direct = new Set<ArtifactType>(
|
|
44
|
+
issues
|
|
45
|
+
.map((issue) => issue.artifactType)
|
|
46
|
+
.filter(
|
|
47
|
+
(artifactType): artifactType is ArtifactType =>
|
|
48
|
+
typeof artifactType === "string" && artifactTypes.includes(artifactType)
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
if (direct.size === 0) return artifactTypes;
|
|
52
|
+
|
|
53
|
+
const defs = await listArtifactDefinitions(cwd);
|
|
54
|
+
let changed = true;
|
|
55
|
+
while (changed) {
|
|
56
|
+
changed = false;
|
|
57
|
+
for (const def of defs) {
|
|
58
|
+
const needsRefresh = def.upstream.some((upstream) => direct.has(upstream));
|
|
59
|
+
if (needsRefresh && !direct.has(def.name)) {
|
|
60
|
+
direct.add(def.name);
|
|
61
|
+
changed = true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return artifactTypes.filter((artifactType) => direct.has(artifactType));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function buildFixProposal(options: FixOptions): Promise<FixProposal> {
|
|
70
|
+
const { cwd, strict, report } = options;
|
|
71
|
+
|
|
72
|
+
const initialReport = await runValidate(cwd, {
|
|
73
|
+
strict: Boolean(strict),
|
|
74
|
+
report
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (initialReport.pass) {
|
|
78
|
+
return {
|
|
79
|
+
targets: [],
|
|
80
|
+
issues: [],
|
|
81
|
+
issuesByArtifact: new Map(),
|
|
82
|
+
initialReport
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const artifactTypes = await listArtifactTypes(cwd);
|
|
87
|
+
const targets = await resolveFixTargets(cwd, artifactTypes, initialReport.issues);
|
|
88
|
+
|
|
89
|
+
const issuesByArtifact = new Map<ArtifactType, ValidationIssue[]>();
|
|
90
|
+
for (const issue of initialReport.issues) {
|
|
91
|
+
if (issue.artifactType) {
|
|
92
|
+
const existing = issuesByArtifact.get(issue.artifactType) ?? [];
|
|
93
|
+
existing.push(issue);
|
|
94
|
+
issuesByArtifact.set(issue.artifactType, existing);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
targets,
|
|
100
|
+
issues: initialReport.issues,
|
|
101
|
+
issuesByArtifact,
|
|
102
|
+
initialReport
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function backupBasePath(cwd: string): string {
|
|
107
|
+
return path.join(prodoPath(cwd), "state", "backups");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function createBackup(
|
|
111
|
+
cwd: string,
|
|
112
|
+
targets: ArtifactType[]
|
|
113
|
+
): Promise<string> {
|
|
114
|
+
const slug = timestampSlug();
|
|
115
|
+
const backupDir = path.join(backupBasePath(cwd), slug);
|
|
116
|
+
await ensureDir(backupDir);
|
|
117
|
+
|
|
118
|
+
const manifest: Array<{ type: ArtifactType; source: string; dest: string }> = [];
|
|
119
|
+
|
|
120
|
+
for (const type of targets) {
|
|
121
|
+
const activePath = await getActiveArtifactPath(cwd, type);
|
|
122
|
+
if (!activePath || !(await fileExists(activePath))) continue;
|
|
123
|
+
|
|
124
|
+
const relPath = path.relative(cwd, activePath);
|
|
125
|
+
const destPath = path.join(backupDir, relPath);
|
|
126
|
+
await ensureDir(path.dirname(destPath));
|
|
127
|
+
await fs.copyFile(activePath, destPath);
|
|
128
|
+
manifest.push({ type, source: activePath, dest: destPath });
|
|
129
|
+
|
|
130
|
+
const parsed = path.parse(activePath);
|
|
131
|
+
const sidecar = path.join(parsed.dir, `${parsed.name}.artifact.json`);
|
|
132
|
+
if (await fileExists(sidecar)) {
|
|
133
|
+
const sidecarRel = path.relative(cwd, sidecar);
|
|
134
|
+
const sidecarDest = path.join(backupDir, sidecarRel);
|
|
135
|
+
await fs.copyFile(sidecar, sidecarDest);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const companionExtensions = type === "workflow" ? [".mmd"] : type === "wireframe" ? [".html"] : [];
|
|
139
|
+
for (const ext of companionExtensions) {
|
|
140
|
+
const companionPath = path.join(parsed.dir, `${parsed.name}${ext}`);
|
|
141
|
+
if (await fileExists(companionPath)) {
|
|
142
|
+
const companionRel = path.relative(cwd, companionPath);
|
|
143
|
+
const companionDest = path.join(backupDir, companionRel);
|
|
144
|
+
await ensureDir(path.dirname(companionDest));
|
|
145
|
+
await fs.copyFile(companionPath, companionDest);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
await fs.writeFile(
|
|
151
|
+
path.join(backupDir, "_manifest.json"),
|
|
152
|
+
`${JSON.stringify({ timestamp: slug, targets, files: manifest }, null, 2)}\n`,
|
|
153
|
+
"utf8"
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
return backupDir;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function restoreBackup(cwd: string, backupDir: string): Promise<void> {
|
|
160
|
+
const manifestPath = path.join(backupDir, "_manifest.json");
|
|
161
|
+
if (!(await fileExists(manifestPath))) {
|
|
162
|
+
throw new UserError("Backup manifest not found. Cannot restore.");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8")) as {
|
|
166
|
+
files: Array<{ source: string; dest: string }>;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
for (const entry of manifest.files) {
|
|
170
|
+
if (await fileExists(entry.dest)) {
|
|
171
|
+
await ensureDir(path.dirname(entry.source));
|
|
172
|
+
await fs.copyFile(entry.dest, entry.source);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function applyFix(
|
|
178
|
+
cwd: string,
|
|
179
|
+
proposal: FixProposal,
|
|
180
|
+
options: FixOptions
|
|
181
|
+
): Promise<FixResult> {
|
|
182
|
+
const { agent, strict, report, log = console.log } = options;
|
|
183
|
+
const backupDir = await createBackup(cwd, proposal.targets);
|
|
184
|
+
|
|
185
|
+
await runHookPhase(cwd, "before_normalize", log);
|
|
186
|
+
const normalizedPath = await runNormalize({ cwd });
|
|
187
|
+
log(`Normalized brief refreshed: ${normalizedPath}`);
|
|
188
|
+
await runHookPhase(cwd, "after_normalize", log);
|
|
189
|
+
|
|
190
|
+
for (const type of proposal.targets) {
|
|
191
|
+
await runHookPhase(cwd, `before_${type}`, log);
|
|
192
|
+
const agentResolved = agent ?? undefined;
|
|
193
|
+
const file = await generateArtifact({
|
|
194
|
+
artifactType: type,
|
|
195
|
+
cwd,
|
|
196
|
+
normalizedBriefOverride: normalizedPath,
|
|
197
|
+
agent: agentResolved,
|
|
198
|
+
revisionType: "fix"
|
|
199
|
+
});
|
|
200
|
+
log(`${type.toUpperCase()} regenerated (fix): ${file}`);
|
|
201
|
+
await runHookPhase(cwd, `after_${type}`, log);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
await runHookPhase(cwd, "before_validate", log);
|
|
205
|
+
const finalResult = await runValidate(cwd, {
|
|
206
|
+
strict: Boolean(strict),
|
|
207
|
+
report
|
|
208
|
+
});
|
|
209
|
+
await runHookPhase(cwd, "after_validate", log);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
proposal,
|
|
213
|
+
applied: true,
|
|
214
|
+
finalPass: finalResult.pass,
|
|
215
|
+
reportPath: finalResult.reportPath,
|
|
216
|
+
backupDir
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function runFix(options: FixOptions): Promise<FixResult> {
|
|
221
|
+
const { cwd, dryRun, log = console.log } = options;
|
|
222
|
+
|
|
223
|
+
const proposal = await buildFixProposal(options);
|
|
224
|
+
|
|
225
|
+
if (proposal.targets.length === 0) {
|
|
226
|
+
log("No blocking issues found. Nothing to fix.");
|
|
227
|
+
return {
|
|
228
|
+
proposal,
|
|
229
|
+
applied: false,
|
|
230
|
+
finalPass: true,
|
|
231
|
+
reportPath: proposal.initialReport.reportPath
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (dryRun) {
|
|
236
|
+
log(`[Dry Run] Would regenerate: ${proposal.targets.join(", ")}`);
|
|
237
|
+
log(`[Dry Run] ${proposal.issues.length} issue(s) identified.`);
|
|
238
|
+
for (const [type, issues] of proposal.issuesByArtifact) {
|
|
239
|
+
log(` ${type}: ${issues.length} issue(s)`);
|
|
240
|
+
for (const issue of issues) {
|
|
241
|
+
log(` - [${issue.level}] ${issue.code}: ${issue.message}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
proposal,
|
|
246
|
+
applied: false,
|
|
247
|
+
finalPass: false,
|
|
248
|
+
reportPath: proposal.initialReport.reportPath
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return applyFix(cwd, proposal, options);
|
|
253
|
+
}
|