@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
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
import { UserError } from "
|
|
2
|
-
|
|
3
|
-
export const AGENT_IDS = ["codex", "gemini-cli", "claude-cli"] as const;
|
|
4
|
-
export type AgentId = (typeof AGENT_IDS)[number];
|
|
5
|
-
|
|
6
|
-
export function isSupportedAgent(agent?: string): agent is AgentId {
|
|
7
|
-
if (!agent) return false;
|
|
8
|
-
return AGENT_IDS.includes(agent as AgentId);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function resolveAgent(agent?: string): AgentId | undefined {
|
|
12
|
-
if (!agent) return undefined;
|
|
13
|
-
if (!isSupportedAgent(agent)) {
|
|
14
|
-
throw new UserError(`Unsupported agent: ${agent}. Supported: ${AGENT_IDS.join(", ")}`);
|
|
15
|
-
}
|
|
16
|
-
return agent;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type CommandSetItem = {
|
|
20
|
-
command: string;
|
|
21
|
-
purpose: string;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type AgentCommandSet = {
|
|
25
|
-
agent: string;
|
|
26
|
-
description?: string;
|
|
27
|
-
recommended_sequence?: CommandSetItem[];
|
|
28
|
-
artifact_shortcuts?: Record<string, string>;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export async function loadAgentCommandSet(_cwd: string, agent: AgentId): Promise<AgentCommandSet> {
|
|
32
|
-
const prefix = "/prodo";
|
|
33
|
-
return {
|
|
34
|
-
agent,
|
|
35
|
-
description: "Agent-specific command set for Prodo artifact pipeline.",
|
|
36
|
-
recommended_sequence: [
|
|
37
|
-
{ command: "prodo init . --ai <agent> --lang <en|tr>", purpose: "Initialize Prodo scaffold and agent commands." },
|
|
38
|
-
{ command: `${prefix}-normalize`, purpose: "Normalize start brief into normalized brief JSON." },
|
|
39
|
-
{ command: `${prefix}-prd`, purpose: "Generate PRD artifact." },
|
|
40
|
-
{ command: `${prefix}-workflow`, purpose: "Generate workflow artifact." },
|
|
41
|
-
{ command: `${prefix}-wireframe`, purpose: "Generate wireframe artifact." },
|
|
42
|
-
{ command: `${prefix}-stories`, purpose: "Generate stories artifact." },
|
|
43
|
-
{ command: `${prefix}-techspec`, purpose: "Generate techspec artifact." },
|
|
44
|
-
{ command: `${prefix}-validate`, purpose: "Run validation report." },
|
|
45
|
-
{ command: `${prefix}-fix`, purpose: "Fix artifacts when validation fails." }
|
|
46
|
-
],
|
|
47
|
-
artifact_shortcuts: {
|
|
48
|
-
normalize: `${prefix}-normalize`,
|
|
49
|
-
prd: `${prefix}-prd`,
|
|
50
|
-
workflow: `${prefix}-workflow`,
|
|
51
|
-
wireframe: `${prefix}-wireframe`,
|
|
52
|
-
stories: `${prefix}-stories`,
|
|
53
|
-
techspec: `${prefix}-techspec`,
|
|
54
|
-
validate: `${prefix}-validate`,
|
|
55
|
-
fix: `${prefix}-fix`
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
}
|
|
1
|
+
import { UserError } from "../core/errors";
|
|
2
|
+
|
|
3
|
+
export const AGENT_IDS = ["codex", "gemini-cli", "claude-cli"] as const;
|
|
4
|
+
export type AgentId = (typeof AGENT_IDS)[number];
|
|
5
|
+
|
|
6
|
+
export function isSupportedAgent(agent?: string): agent is AgentId {
|
|
7
|
+
if (!agent) return false;
|
|
8
|
+
return AGENT_IDS.includes(agent as AgentId);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveAgent(agent?: string): AgentId | undefined {
|
|
12
|
+
if (!agent) return undefined;
|
|
13
|
+
if (!isSupportedAgent(agent)) {
|
|
14
|
+
throw new UserError(`Unsupported agent: ${agent}. Supported: ${AGENT_IDS.join(", ")}`);
|
|
15
|
+
}
|
|
16
|
+
return agent;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type CommandSetItem = {
|
|
20
|
+
command: string;
|
|
21
|
+
purpose: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type AgentCommandSet = {
|
|
25
|
+
agent: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
recommended_sequence?: CommandSetItem[];
|
|
28
|
+
artifact_shortcuts?: Record<string, string>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export async function loadAgentCommandSet(_cwd: string, agent: AgentId): Promise<AgentCommandSet> {
|
|
32
|
+
const prefix = "/prodo";
|
|
33
|
+
return {
|
|
34
|
+
agent,
|
|
35
|
+
description: "Agent-specific command set for Prodo artifact pipeline.",
|
|
36
|
+
recommended_sequence: [
|
|
37
|
+
{ command: "prodo init . --ai <agent> --lang <en|tr>", purpose: "Initialize Prodo scaffold and agent commands." },
|
|
38
|
+
{ command: `${prefix}-normalize`, purpose: "Normalize start brief into normalized brief JSON." },
|
|
39
|
+
{ command: `${prefix}-prd`, purpose: "Generate PRD artifact." },
|
|
40
|
+
{ command: `${prefix}-workflow`, purpose: "Generate workflow artifact." },
|
|
41
|
+
{ command: `${prefix}-wireframe`, purpose: "Generate wireframe artifact." },
|
|
42
|
+
{ command: `${prefix}-stories`, purpose: "Generate stories artifact." },
|
|
43
|
+
{ command: `${prefix}-techspec`, purpose: "Generate techspec artifact." },
|
|
44
|
+
{ command: `${prefix}-validate`, purpose: "Run validation report." },
|
|
45
|
+
{ command: `${prefix}-fix`, purpose: "Fix artifacts when validation fails." }
|
|
46
|
+
],
|
|
47
|
+
artifact_shortcuts: {
|
|
48
|
+
normalize: `${prefix}-normalize`,
|
|
49
|
+
prd: `${prefix}-prd`,
|
|
50
|
+
workflow: `${prefix}-workflow`,
|
|
51
|
+
wireframe: `${prefix}-wireframe`,
|
|
52
|
+
stories: `${prefix}-stories`,
|
|
53
|
+
techspec: `${prefix}-techspec`,
|
|
54
|
+
validate: `${prefix}-validate`,
|
|
55
|
+
fix: `${prefix}-fix`
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -1,137 +1,157 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (status === "
|
|
37
|
-
return color("
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (status === "
|
|
43
|
-
return color("
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
child
|
|
60
|
-
child.on("
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"██║
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
{
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{ name: "
|
|
110
|
-
{ name: "
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { getGlobalRegistry } from "../agents/agent-registry";
|
|
4
|
+
import { prodoPath } from "../core/paths";
|
|
5
|
+
import { fileExists } from "../core/utils";
|
|
6
|
+
import { readCliVersion } from "../core/version";
|
|
7
|
+
|
|
8
|
+
type RowStatus = "available" | "not_found" | "ide";
|
|
9
|
+
|
|
10
|
+
type DoctorRow = {
|
|
11
|
+
name: string;
|
|
12
|
+
status: RowStatus;
|
|
13
|
+
detail: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function termWidth(): number {
|
|
17
|
+
return Math.max(80, process.stdout.columns ?? 100);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function stripAnsi(input: string): string {
|
|
21
|
+
return input.replace(/\u001B\[[0-9;]*m/g, "");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function color(input: string, code: string): string {
|
|
25
|
+
if (!process.stdout.isTTY) return input;
|
|
26
|
+
return `${code}${input}\u001B[0m`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function center(input: string, width: number): string {
|
|
30
|
+
const visible = stripAnsi(input).length;
|
|
31
|
+
const left = Math.max(0, Math.floor((width - visible) / 2));
|
|
32
|
+
return `${" ".repeat(left)}${input}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function iconFor(status: RowStatus): string {
|
|
36
|
+
if (status === "available") return color("✔", "\u001B[32m");
|
|
37
|
+
if (status === "not_found") return color("✖", "\u001B[31m");
|
|
38
|
+
return color("•", "\u001B[33m");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function labelFor(status: RowStatus): string {
|
|
42
|
+
if (status === "available") return color("available", "\u001B[32m");
|
|
43
|
+
if (status === "not_found") return color("not found", "\u001B[31m");
|
|
44
|
+
return color("IDE-based", "\u001B[2;33m");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function renderRows(rows: DoctorRow[]): string[] {
|
|
48
|
+
const leftWidth = Math.max(...rows.map((row) => row.name.length), 10);
|
|
49
|
+
return rows.map((row) => {
|
|
50
|
+
const left = row.name.padEnd(leftWidth, " ");
|
|
51
|
+
const status = `${labelFor(row.status)}${row.detail ? ` (${row.detail})` : ""}`;
|
|
52
|
+
return ` ${iconFor(row.status)} ${left} ${status}`;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function commandExists(command: string): Promise<boolean> {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const lookup = process.platform === "win32" ? "where" : "which";
|
|
59
|
+
const child = spawn(lookup, [command], { stdio: "ignore" });
|
|
60
|
+
child.on("error", () => resolve(false));
|
|
61
|
+
child.on("close", (code) => resolve(code === 0));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function firstAvailable(commands: string[]): Promise<boolean> {
|
|
66
|
+
for (const command of commands) {
|
|
67
|
+
if (await commandExists(command)) return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function renderLogo(width: number): string {
|
|
73
|
+
const cyan = "\u001B[38;5;45m";
|
|
74
|
+
const blue = "\u001B[38;5;39m";
|
|
75
|
+
const logo = [
|
|
76
|
+
"██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ",
|
|
77
|
+
"██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██╔═══██╗",
|
|
78
|
+
"██████╔╝██████╔╝██║ ██║██║ ██║██║ ██║",
|
|
79
|
+
"██╔═══╝ ██╔══██╗██║ ██║██║ ██║██║ ██║",
|
|
80
|
+
"██║ ██║ ██║╚██████╔╝██████╔╝╚██████╔╝"
|
|
81
|
+
];
|
|
82
|
+
const painted = logo.map((line, idx) => color(line, idx % 2 === 0 ? cyan : blue));
|
|
83
|
+
return painted.map((line) => center(line, width)).join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function runDoctor(cwd: string, out: (line: string) => void): Promise<void> {
|
|
87
|
+
const width = termWidth();
|
|
88
|
+
const version = await readCliVersion(cwd);
|
|
89
|
+
const isInitialized = await fileExists(prodoPath(cwd));
|
|
90
|
+
|
|
91
|
+
const codex = await firstAvailable(["codex"]);
|
|
92
|
+
const gemini = await firstAvailable(["gemini", "gemini-cli"]);
|
|
93
|
+
const claude = await firstAvailable(["claude", "claude-cli"]);
|
|
94
|
+
|
|
95
|
+
const git = await firstAvailable(["git"]);
|
|
96
|
+
const node = await firstAvailable(["node"]);
|
|
97
|
+
const vscode = await firstAvailable(["code"]);
|
|
98
|
+
|
|
99
|
+
const coreRows: DoctorRow[] = [
|
|
100
|
+
{ name: "Prodo CLI", status: "available", detail: `v${version}` },
|
|
101
|
+
{
|
|
102
|
+
name: "Project initialized",
|
|
103
|
+
status: isInitialized ? "available" : "not_found",
|
|
104
|
+
detail: isInitialized ? ".prodo found" : ".prodo missing"
|
|
105
|
+
}
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
const aiCliRows: DoctorRow[] = [
|
|
109
|
+
{ name: "Codex CLI", status: codex ? "available" : "not_found", detail: codex ? "available" : "not found" },
|
|
110
|
+
{ name: "Gemini CLI", status: gemini ? "available" : "not_found", detail: gemini ? "available" : "not found" },
|
|
111
|
+
{ name: "Claude CLI", status: claude ? "available" : "not_found", detail: claude ? "available" : "not found" }
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
const registry = getGlobalRegistry();
|
|
115
|
+
const agentRows: DoctorRow[] = [];
|
|
116
|
+
for (const agent of registry.list()) {
|
|
117
|
+
if (agent.name === "mock") continue;
|
|
118
|
+
const available = await agent.isAvailable();
|
|
119
|
+
const config = agent.getConfig();
|
|
120
|
+
const sdkLabel = config.sdkRequired ? `SDK: ${config.sdkRequired}` : "";
|
|
121
|
+
const envLabel = config.envVars.filter((v) => !process.env[v]).map((v) => `${v} missing`).join(", ");
|
|
122
|
+
const detail = available ? "ready" : [sdkLabel, envLabel].filter(Boolean).join(", ") || "not configured";
|
|
123
|
+
agentRows.push({
|
|
124
|
+
name: config.displayName,
|
|
125
|
+
status: available ? "available" : "not_found",
|
|
126
|
+
detail
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const devRows: DoctorRow[] = [
|
|
131
|
+
{ name: "Git", status: git ? "available" : "not_found", detail: git ? "available" : "not found" },
|
|
132
|
+
{ name: "Node.js", status: node ? "available" : "not_found", detail: node ? "available" : "not found" },
|
|
133
|
+
{ name: "Visual Studio Code", status: vscode ? "available" : "not_found", detail: vscode ? "available" : "not found" },
|
|
134
|
+
{ name: "Cursor", status: "ide", detail: "IDE-based, no CLI check" }
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
out("");
|
|
138
|
+
out(renderLogo(width));
|
|
139
|
+
out("");
|
|
140
|
+
out(center(color("Prodo — AI-Powered Product Owner", "\u001B[1;37m"), width));
|
|
141
|
+
out(center(color("Built by Shahmarasy · Works with Claude, Codex, and Gemini", "\u001B[2;37m"), width));
|
|
142
|
+
out("");
|
|
143
|
+
out("Checking environment...");
|
|
144
|
+
out("");
|
|
145
|
+
out(color("Core", "\u001B[1m"));
|
|
146
|
+
for (const line of renderRows(coreRows)) out(line);
|
|
147
|
+
out("");
|
|
148
|
+
out(color("AI CLI Tools", "\u001B[1m"));
|
|
149
|
+
for (const line of renderRows(aiCliRows)) out(line);
|
|
150
|
+
out("");
|
|
151
|
+
out(color("LLM Agents (SDK)", "\u001B[1m"));
|
|
152
|
+
for (const line of renderRows(agentRows)) out(line);
|
|
153
|
+
out("");
|
|
154
|
+
out(color("Dev Tools", "\u001B[1m"));
|
|
155
|
+
for (const line of renderRows(devRows)) out(line);
|
|
156
|
+
out("");
|
|
157
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { FixProposal, FixResult } from "../core/fix";
|
|
2
|
+
|
|
3
|
+
const dynamicImport = new Function("specifier", "return import(specifier)") as (
|
|
4
|
+
specifier: string
|
|
5
|
+
) => Promise<unknown>;
|
|
6
|
+
|
|
7
|
+
type ClackModule = {
|
|
8
|
+
intro: (title: string) => void;
|
|
9
|
+
outro: (message: string) => void;
|
|
10
|
+
cancel: (message: string) => void;
|
|
11
|
+
confirm: (opts: { message: string }) => Promise<boolean | symbol>;
|
|
12
|
+
isCancel: (value: unknown) => boolean;
|
|
13
|
+
log: {
|
|
14
|
+
info: (message: string) => void;
|
|
15
|
+
warn: (message: string) => void;
|
|
16
|
+
error: (message: string) => void;
|
|
17
|
+
step: (message: string) => void;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
async function loadClack(): Promise<ClackModule | null> {
|
|
22
|
+
try {
|
|
23
|
+
return (await dynamicImport("@clack/prompts")) as ClackModule;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function displayFixProposal(
|
|
30
|
+
proposal: FixProposal,
|
|
31
|
+
log: (message: string) => void
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
const errorCount = proposal.issues.filter((i) => i.level === "error").length;
|
|
34
|
+
const warningCount = proposal.issues.filter((i) => i.level === "warning").length;
|
|
35
|
+
|
|
36
|
+
log("");
|
|
37
|
+
log(`Fix Proposal`);
|
|
38
|
+
log(`${"─".repeat(50)}`);
|
|
39
|
+
log(`Issues found: ${errorCount} error(s), ${warningCount} warning(s)`);
|
|
40
|
+
log(`Artifacts to regenerate: ${proposal.targets.join(", ")}`);
|
|
41
|
+
log("");
|
|
42
|
+
|
|
43
|
+
for (const [type, issues] of proposal.issuesByArtifact) {
|
|
44
|
+
log(` ${type.toUpperCase()} (${issues.length} issue${issues.length > 1 ? "s" : ""}):`);
|
|
45
|
+
for (const issue of issues) {
|
|
46
|
+
const prefix = issue.level === "error" ? " ✖" : " ⚠";
|
|
47
|
+
log(` ${prefix} [${issue.code}] ${issue.message}`);
|
|
48
|
+
if (issue.suggestion) {
|
|
49
|
+
log(` → ${issue.suggestion}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
log("");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const orphanIssues = proposal.issues.filter((i) => !i.artifactType);
|
|
56
|
+
if (orphanIssues.length > 0) {
|
|
57
|
+
log(` General (${orphanIssues.length} issue${orphanIssues.length > 1 ? "s" : ""}):`);
|
|
58
|
+
for (const issue of orphanIssues) {
|
|
59
|
+
const prefix = issue.level === "error" ? " ✖" : " ⚠";
|
|
60
|
+
log(` ${prefix} [${issue.code}] ${issue.message}`);
|
|
61
|
+
}
|
|
62
|
+
log("");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function confirmFixExecution(
|
|
67
|
+
proposal: FixProposal
|
|
68
|
+
): Promise<boolean> {
|
|
69
|
+
if (process.stdout.isTTY !== true) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const clack = await loadClack();
|
|
74
|
+
if (!clack) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
clack.intro("Prodo Fix");
|
|
79
|
+
|
|
80
|
+
const proceed = await clack.confirm({
|
|
81
|
+
message: `Regenerate ${proposal.targets.length} artifact(s): ${proposal.targets.join(", ")}?`
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (clack.isCancel(proceed)) {
|
|
85
|
+
clack.cancel("Fix cancelled.");
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return proceed === true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function displayFixResult(
|
|
93
|
+
result: FixResult,
|
|
94
|
+
log: (message: string) => void
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
log("");
|
|
97
|
+
if (result.finalPass) {
|
|
98
|
+
log("Fix complete — validation passed.");
|
|
99
|
+
} else {
|
|
100
|
+
log("Fix applied but validation still failing.");
|
|
101
|
+
log(`Review report: ${result.reportPath}`);
|
|
102
|
+
if (result.backupDir) {
|
|
103
|
+
log(`Backup saved: ${result.backupDir}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const clack = await loadClack();
|
|
108
|
+
if (clack && process.stdout.isTTY) {
|
|
109
|
+
clack.outro(result.finalPass ? "All gates passed!" : "Review report and retry.");
|
|
110
|
+
}
|
|
111
|
+
}
|