@shahmarasy/prodo 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +201 -97
  2. package/bin/prodo.cjs +6 -6
  3. package/dist/agents/agent-registry.d.ts +13 -0
  4. package/dist/agents/agent-registry.js +79 -0
  5. package/dist/agents/anthropic/index.d.ts +9 -0
  6. package/dist/agents/anthropic/index.js +55 -0
  7. package/dist/agents/base.d.ts +25 -0
  8. package/dist/agents/base.js +71 -0
  9. package/dist/agents/google/index.d.ts +9 -0
  10. package/dist/agents/google/index.js +53 -0
  11. package/dist/agents/mock/index.d.ts +11 -0
  12. package/dist/agents/mock/index.js +26 -0
  13. package/dist/agents/openai/index.d.ts +9 -0
  14. package/dist/agents/openai/index.js +57 -0
  15. package/dist/agents/system-prompts.d.ts +3 -0
  16. package/dist/agents/system-prompts.js +32 -0
  17. package/dist/cli/agent-command-installer.d.ts +4 -0
  18. package/dist/cli/agent-command-installer.js +148 -0
  19. package/dist/cli/agent-ids.d.ts +15 -0
  20. package/dist/cli/agent-ids.js +49 -0
  21. package/dist/cli/doctor.d.ts +1 -0
  22. package/dist/cli/doctor.js +144 -0
  23. package/dist/cli/fix-tui.d.ts +4 -0
  24. package/dist/cli/fix-tui.js +79 -0
  25. package/dist/cli/index.d.ts +9 -0
  26. package/dist/cli/index.js +465 -0
  27. package/dist/cli/init-tui.d.ts +23 -0
  28. package/dist/cli/init-tui.js +176 -0
  29. package/dist/cli/init.d.ts +11 -0
  30. package/dist/cli/init.js +334 -0
  31. package/dist/cli/normalize-interactive.d.ts +8 -0
  32. package/dist/cli/normalize-interactive.js +167 -0
  33. package/dist/cli/preset-loader.d.ts +4 -0
  34. package/dist/cli/preset-loader.js +210 -0
  35. package/dist/core/artifact-registry.d.ts +11 -0
  36. package/dist/core/artifact-registry.js +49 -0
  37. package/dist/core/artifacts.d.ts +10 -0
  38. package/dist/core/artifacts.js +892 -0
  39. package/dist/core/clean.d.ts +10 -0
  40. package/dist/core/clean.js +74 -0
  41. package/dist/core/consistency.d.ts +8 -0
  42. package/dist/core/consistency.js +328 -0
  43. package/dist/core/constants.d.ts +7 -0
  44. package/dist/core/constants.js +64 -0
  45. package/dist/core/errors.d.ts +3 -0
  46. package/dist/core/errors.js +10 -0
  47. package/dist/core/fix.d.ts +31 -0
  48. package/dist/core/fix.js +188 -0
  49. package/dist/core/hook-executor.d.ts +1 -0
  50. package/dist/core/hook-executor.js +175 -0
  51. package/dist/core/markdown.d.ts +16 -0
  52. package/dist/core/markdown.js +81 -0
  53. package/dist/core/normalize.d.ts +8 -0
  54. package/dist/core/normalize.js +125 -0
  55. package/dist/core/normalized-brief.d.ts +48 -0
  56. package/dist/core/normalized-brief.js +182 -0
  57. package/dist/core/output-index.d.ts +13 -0
  58. package/dist/core/output-index.js +55 -0
  59. package/dist/core/paths.d.ts +17 -0
  60. package/dist/core/paths.js +80 -0
  61. package/dist/core/project-config.d.ts +14 -0
  62. package/dist/core/project-config.js +69 -0
  63. package/dist/core/registry.d.ts +13 -0
  64. package/dist/core/registry.js +115 -0
  65. package/dist/core/settings.d.ts +7 -0
  66. package/dist/core/settings.js +35 -0
  67. package/dist/core/template-engine.d.ts +3 -0
  68. package/dist/core/template-engine.js +43 -0
  69. package/dist/core/template-resolver.d.ts +15 -0
  70. package/dist/core/template-resolver.js +46 -0
  71. package/dist/core/templates.d.ts +33 -0
  72. package/dist/core/templates.js +440 -0
  73. package/dist/core/terminology.d.ts +21 -0
  74. package/dist/core/terminology.js +143 -0
  75. package/dist/core/tracing.d.ts +21 -0
  76. package/dist/core/tracing.js +74 -0
  77. package/dist/core/types.d.ts +35 -0
  78. package/dist/core/types.js +5 -0
  79. package/dist/core/utils.d.ts +7 -0
  80. package/dist/core/utils.js +66 -0
  81. package/dist/core/validate.d.ts +10 -0
  82. package/dist/core/validate.js +226 -0
  83. package/dist/core/validator.d.ts +5 -0
  84. package/dist/core/validator.js +76 -0
  85. package/dist/core/version.d.ts +1 -0
  86. package/dist/core/version.js +30 -0
  87. package/dist/core/workflow-commands.d.ts +7 -0
  88. package/dist/core/workflow-commands.js +29 -0
  89. package/dist/i18n/en.json +45 -0
  90. package/dist/i18n/index.d.ts +5 -0
  91. package/dist/i18n/index.js +63 -0
  92. package/dist/i18n/tr.json +45 -0
  93. package/dist/providers/index.d.ts +2 -1
  94. package/dist/providers/index.js +20 -6
  95. package/dist/providers/mock-provider.d.ts +1 -1
  96. package/dist/providers/mock-provider.js +7 -6
  97. package/dist/providers/openai-provider.d.ts +1 -1
  98. package/dist/providers/openai-provider.js +1 -1
  99. package/dist/skills/engine.d.ts +10 -0
  100. package/dist/skills/engine.js +75 -0
  101. package/dist/skills/fix-skill.d.ts +2 -0
  102. package/dist/skills/fix-skill.js +38 -0
  103. package/dist/skills/generate-artifact-skill.d.ts +2 -0
  104. package/dist/skills/generate-artifact-skill.js +32 -0
  105. package/dist/skills/generate-pipeline-skill.d.ts +2 -0
  106. package/dist/skills/generate-pipeline-skill.js +45 -0
  107. package/dist/skills/normalize-skill.d.ts +2 -0
  108. package/dist/skills/normalize-skill.js +29 -0
  109. package/dist/skills/types.d.ts +28 -0
  110. package/dist/skills/types.js +2 -0
  111. package/dist/skills/validate-skill.d.ts +2 -0
  112. package/dist/skills/validate-skill.js +29 -0
  113. package/package.json +74 -45
  114. package/src/agents/agent-registry.ts +93 -0
  115. package/src/agents/anthropic/index.ts +86 -0
  116. package/src/agents/anthropic/manifest.json +7 -0
  117. package/src/agents/base.ts +77 -0
  118. package/src/agents/google/index.ts +79 -0
  119. package/src/agents/google/manifest.json +7 -0
  120. package/src/agents/mock/index.ts +32 -0
  121. package/src/agents/mock/manifest.json +7 -0
  122. package/src/agents/openai/index.ts +83 -0
  123. package/src/agents/openai/manifest.json +7 -0
  124. package/src/agents/system-prompts.ts +35 -0
  125. package/src/{agent-command-installer.ts → cli/agent-command-installer.ts} +164 -164
  126. package/src/{agents.ts → cli/agent-ids.ts} +58 -58
  127. package/src/{doctor.ts → cli/doctor.ts} +157 -137
  128. package/src/cli/fix-tui.ts +111 -0
  129. package/src/{cli.ts → cli/index.ts} +459 -410
  130. package/src/{init-tui.ts → cli/init-tui.ts} +208 -208
  131. package/src/{init.ts → cli/init.ts} +398 -398
  132. package/src/cli/normalize-interactive.ts +241 -0
  133. package/src/{preset-loader.ts → cli/preset-loader.ts} +237 -237
  134. package/src/{artifact-registry.ts → core/artifact-registry.ts} +69 -69
  135. package/src/{artifacts.ts → core/artifacts.ts} +1081 -1072
  136. package/src/core/clean.ts +88 -0
  137. package/src/{consistency.ts → core/consistency.ts} +374 -303
  138. package/src/{constants.ts → core/constants.ts} +72 -72
  139. package/src/{errors.ts → core/errors.ts} +7 -7
  140. package/src/core/fix.ts +253 -0
  141. package/src/{hook-executor.ts → core/hook-executor.ts} +196 -196
  142. package/src/{markdown.ts → core/markdown.ts} +93 -73
  143. package/src/{normalize.ts → core/normalize.ts} +145 -137
  144. package/src/{normalized-brief.ts → core/normalized-brief.ts} +227 -206
  145. package/src/{output-index.ts → core/output-index.ts} +59 -59
  146. package/src/{paths.ts → core/paths.ts} +75 -71
  147. package/src/{project-config.ts → core/project-config.ts} +78 -78
  148. package/src/{registry.ts → core/registry.ts} +119 -119
  149. package/src/{settings.ts → core/settings.ts} +35 -35
  150. package/src/core/template-engine.ts +45 -0
  151. package/src/{template-resolver.ts → core/template-resolver.ts} +54 -54
  152. package/src/{templates.ts → core/templates.ts} +452 -452
  153. package/src/core/terminology.ts +177 -0
  154. package/src/core/tracing.ts +110 -0
  155. package/src/{types.ts → core/types.ts} +46 -46
  156. package/src/{utils.ts → core/utils.ts} +64 -64
  157. package/src/{validate.ts → core/validate.ts} +252 -246
  158. package/src/{validator.ts → core/validator.ts} +92 -92
  159. package/src/{version.ts → core/version.ts} +24 -24
  160. package/src/{workflow-commands.ts → core/workflow-commands.ts} +32 -32
  161. package/src/i18n/en.json +45 -0
  162. package/src/i18n/index.ts +58 -0
  163. package/src/i18n/tr.json +45 -0
  164. package/src/providers/index.ts +29 -12
  165. package/src/providers/mock-provider.ts +200 -199
  166. package/src/providers/openai-provider.ts +88 -88
  167. package/src/skills/engine.ts +94 -0
  168. package/src/skills/fix-skill.ts +38 -0
  169. package/src/skills/generate-artifact-skill.ts +32 -0
  170. package/src/skills/generate-pipeline-skill.ts +49 -0
  171. package/src/skills/normalize-skill.ts +29 -0
  172. package/src/skills/types.ts +36 -0
  173. package/src/skills/validate-skill.ts +29 -0
@@ -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
+
@@ -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
+ }