@shahmarasy/prodo 0.1.5 → 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/dist/agents/system-prompts.js +12 -12
- package/dist/cli/agent-command-installer.js +18 -18
- package/dist/cli/doctor.js +2 -2
- package/dist/cli/index.js +32 -30
- package/dist/cli/init-tui.d.ts +2 -2
- package/dist/cli/init-tui.js +43 -36
- package/dist/cli/init.d.ts +1 -0
- package/dist/cli/init.js +2 -1
- package/dist/core/artifacts.js +72 -72
- package/dist/core/settings.d.ts +1 -0
- package/dist/core/settings.js +10 -2
- package/dist/core/templates.js +248 -248
- package/dist/i18n/en.json +45 -45
- package/dist/i18n/tr.json +45 -45
- package/dist/providers/mock-provider.js +5 -5
- package/dist/providers/openai-provider.js +12 -12
- 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/fix.d.ts +2 -0
- package/dist/skills/fix.js +41 -0
- package/dist/skills/generate-artifact.d.ts +2 -0
- package/dist/skills/generate-artifact.js +42 -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/validate.d.ts +2 -0
- package/dist/skills/validate.js +37 -0
- package/package.json +4 -6
- package/src/cli/doctor.ts +2 -2
- package/src/cli/index.ts +35 -31
- package/src/cli/init-tui.ts +220 -208
- package/src/cli/init.ts +3 -2
- package/src/core/settings.ts +41 -35
- 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-skill.ts → normalize.ts} +15 -12
- package/src/skills/register-core.ts +27 -0
- package/src/skills/validate.ts +40 -0
- package/src/skills/engine.ts +0 -94
- package/src/skills/fix-skill.ts +0 -38
- package/src/skills/generate-artifact-skill.ts +0 -32
- package/src/skills/generate-pipeline-skill.ts +0 -49
- package/src/skills/types.ts +0 -36
- package/src/skills/validate-skill.ts +0 -29
package/src/cli/init-tui.ts
CHANGED
|
@@ -1,208 +1,220 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import { resolveAi, type SupportedAi } from "./agent-command-installer";
|
|
4
|
-
import { UserError } from "../core/errors";
|
|
5
|
-
import { fileExists } from "../core/utils";
|
|
6
|
-
|
|
7
|
-
export type InitSelections = {
|
|
8
|
-
ai?: SupportedAi;
|
|
9
|
-
script: "sh" | "ps";
|
|
10
|
-
lang:
|
|
11
|
-
author: string;
|
|
12
|
-
interactive: boolean;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
type GatherInitUiOptions = {
|
|
16
|
-
projectRoot: string;
|
|
17
|
-
aiInput?: string;
|
|
18
|
-
langInput?: string;
|
|
19
|
-
authorInput?: string;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type ClackPrompts = typeof import("@clack/prompts");
|
|
23
|
-
|
|
24
|
-
const dynamicImport = new Function("specifier", "return import(specifier)") as (
|
|
25
|
-
specifier: string
|
|
26
|
-
) => Promise<unknown>;
|
|
27
|
-
|
|
28
|
-
async function loadClack(): Promise<ClackPrompts> {
|
|
29
|
-
return (await dynamicImport("@clack/prompts")) as ClackPrompts;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function isInteractiveTerminal(): boolean {
|
|
33
|
-
return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function color(text: string, code: string): string {
|
|
37
|
-
return `${code}${text}\u001B[0m`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function stripAnsi(input: string): string {
|
|
41
|
-
return input.replace(/\u001B\[[0-9;]*m/g, "");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function centerLine(line: string, width: number): string {
|
|
45
|
-
const visible = stripAnsi(line).length;
|
|
46
|
-
const left = Math.max(0, Math.floor((width - visible) / 2));
|
|
47
|
-
return `${" ".repeat(left)}${line}`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function centerBlock(lines: string[], width: number): string {
|
|
51
|
-
return lines.map((line) => centerLine(line, width)).join("\n");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function terminalWidth(): number {
|
|
55
|
-
const columns = process.stdout.columns ?? 100;
|
|
56
|
-
return Math.max(80, columns);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function renderLogo(): string {
|
|
60
|
-
const cyan = "\u001B[38;5;45m";
|
|
61
|
-
const blue = "\u001B[38;5;39m";
|
|
62
|
-
const lines = [
|
|
63
|
-
"██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ",
|
|
64
|
-
"██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██╔═══██╗",
|
|
65
|
-
"██████╔╝██████╔╝██║ ██║██║ ██║██║ ██║",
|
|
66
|
-
"██╔═══╝ ██╔══██╗██║ ██║██║ ██║██║ ██║",
|
|
67
|
-
"██║ ██║ ██║╚██████╔╝██████╔╝╚██████╔╝"
|
|
68
|
-
];
|
|
69
|
-
return lines.map((line, idx) => color(line, idx % 2 === 0 ? cyan : blue)).join("\n");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function renderProjectBox(projectName: string, projectRoot: string): string {
|
|
73
|
-
const content = [`Project ${projectName}`, `Directory ${projectRoot}`];
|
|
74
|
-
const innerWidth = Math.max(...content.map((line) => line.length)) + 2;
|
|
75
|
-
const top = `┌${"─".repeat(innerWidth)}┐`;
|
|
76
|
-
const rows = content.map((line) => `│ ${line.padEnd(innerWidth - 1)}│`);
|
|
77
|
-
const bottom = `└${"─".repeat(innerWidth)}┘`;
|
|
78
|
-
return [top, ...rows, bottom].join("\n");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function detectAi(projectRoot: string): Promise<SupportedAi | undefined> {
|
|
82
|
-
if (await fileExists(path.join(projectRoot, ".
|
|
83
|
-
if (await fileExists(path.join(projectRoot, ".
|
|
84
|
-
if (await fileExists(path.join(projectRoot, ".
|
|
85
|
-
return undefined;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function normalizeLang(lang?: string):
|
|
89
|
-
|
|
90
|
-
return "
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { resolveAi, type SupportedAi } from "./agent-command-installer";
|
|
4
|
+
import { UserError } from "../core/errors";
|
|
5
|
+
import { fileExists } from "../core/utils";
|
|
6
|
+
|
|
7
|
+
export type InitSelections = {
|
|
8
|
+
ai?: SupportedAi;
|
|
9
|
+
script: "sh" | "ps";
|
|
10
|
+
lang: string;
|
|
11
|
+
author: string;
|
|
12
|
+
interactive: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type GatherInitUiOptions = {
|
|
16
|
+
projectRoot: string;
|
|
17
|
+
aiInput?: string;
|
|
18
|
+
langInput?: string;
|
|
19
|
+
authorInput?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type ClackPrompts = typeof import("@clack/prompts");
|
|
23
|
+
|
|
24
|
+
const dynamicImport = new Function("specifier", "return import(specifier)") as (
|
|
25
|
+
specifier: string
|
|
26
|
+
) => Promise<unknown>;
|
|
27
|
+
|
|
28
|
+
async function loadClack(): Promise<ClackPrompts> {
|
|
29
|
+
return (await dynamicImport("@clack/prompts")) as ClackPrompts;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isInteractiveTerminal(): boolean {
|
|
33
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function color(text: string, code: string): string {
|
|
37
|
+
return `${code}${text}\u001B[0m`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function stripAnsi(input: string): string {
|
|
41
|
+
return input.replace(/\u001B\[[0-9;]*m/g, "");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function centerLine(line: string, width: number): string {
|
|
45
|
+
const visible = stripAnsi(line).length;
|
|
46
|
+
const left = Math.max(0, Math.floor((width - visible) / 2));
|
|
47
|
+
return `${" ".repeat(left)}${line}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function centerBlock(lines: string[], width: number): string {
|
|
51
|
+
return lines.map((line) => centerLine(line, width)).join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function terminalWidth(): number {
|
|
55
|
+
const columns = process.stdout.columns ?? 100;
|
|
56
|
+
return Math.max(80, columns);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function renderLogo(): string {
|
|
60
|
+
const cyan = "\u001B[38;5;45m";
|
|
61
|
+
const blue = "\u001B[38;5;39m";
|
|
62
|
+
const lines = [
|
|
63
|
+
"██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ",
|
|
64
|
+
"██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██╔═══██╗",
|
|
65
|
+
"██████╔╝██████╔╝██║ ██║██║ ██║██║ ██║",
|
|
66
|
+
"██╔═══╝ ██╔══██╗██║ ██║██║ ██║██║ ██║",
|
|
67
|
+
"██║ ██║ ██║╚██████╔╝██████╔╝╚██████╔╝"
|
|
68
|
+
];
|
|
69
|
+
return lines.map((line, idx) => color(line, idx % 2 === 0 ? cyan : blue)).join("\n");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function renderProjectBox(projectName: string, projectRoot: string): string {
|
|
73
|
+
const content = [`Project ${projectName}`, `Directory ${projectRoot}`];
|
|
74
|
+
const innerWidth = Math.max(...content.map((line) => line.length)) + 2;
|
|
75
|
+
const top = `┌${"─".repeat(innerWidth)}┐`;
|
|
76
|
+
const rows = content.map((line) => `│ ${line.padEnd(innerWidth - 1)}│`);
|
|
77
|
+
const bottom = `└${"─".repeat(innerWidth)}┘`;
|
|
78
|
+
return [top, ...rows, bottom].join("\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function detectAi(projectRoot: string): Promise<SupportedAi | undefined> {
|
|
82
|
+
if (await fileExists(path.join(projectRoot, ".claude"))) return "claude-cli";
|
|
83
|
+
if (await fileExists(path.join(projectRoot, ".agents"))) return "codex";
|
|
84
|
+
if (await fileExists(path.join(projectRoot, ".gemini"))) return "gemini-cli";
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeLang(lang?: string): string {
|
|
89
|
+
const raw = (lang ?? "").trim().toLowerCase();
|
|
90
|
+
if (raw.startsWith("tr")) return "tr";
|
|
91
|
+
return "en";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function defaultAuthorName(authorInput?: string): string {
|
|
95
|
+
const explicit = (authorInput ?? "").trim();
|
|
96
|
+
if (explicit.length > 0) return explicit;
|
|
97
|
+
try {
|
|
98
|
+
const username = os.userInfo().username.trim();
|
|
99
|
+
if (username.length > 0) return username;
|
|
100
|
+
} catch {
|
|
101
|
+
// ignore
|
|
102
|
+
}
|
|
103
|
+
return "Product Author";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function gatherInitSelections(options: GatherInitUiOptions): Promise<InitSelections> {
|
|
107
|
+
const clack = await loadClack();
|
|
108
|
+
const defaultLang = normalizeLang(options.langInput);
|
|
109
|
+
const fallbackScript: "sh" | "ps" = process.platform === "win32" ? "ps" : "sh";
|
|
110
|
+
const parsedAi = resolveAi(options.aiInput);
|
|
111
|
+
const defaultAuthor = defaultAuthorName(options.authorInput);
|
|
112
|
+
|
|
113
|
+
if (!isInteractiveTerminal()) {
|
|
114
|
+
return {
|
|
115
|
+
ai: parsedAi,
|
|
116
|
+
script: fallbackScript,
|
|
117
|
+
lang: defaultLang,
|
|
118
|
+
author: defaultAuthor,
|
|
119
|
+
interactive: false
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const detectedAi = await detectAi(options.projectRoot);
|
|
124
|
+
const projectName = path.basename(options.projectRoot) || ".";
|
|
125
|
+
const width = terminalWidth();
|
|
126
|
+
const subtitle = color("Prodo — AI-Powered Product Owner", "\u001B[1;37m");
|
|
127
|
+
const signature = color("Built by Shahmarasy · Works with Claude, Codex, and Gemini", "\u001B[38;5;244m");
|
|
128
|
+
const hero = [
|
|
129
|
+
"",
|
|
130
|
+
centerBlock(renderLogo().split("\n"), width),
|
|
131
|
+
"",
|
|
132
|
+
centerLine(subtitle, width),
|
|
133
|
+
centerLine(signature, width),
|
|
134
|
+
""
|
|
135
|
+
].join("\n");
|
|
136
|
+
|
|
137
|
+
clack.intro(hero);
|
|
138
|
+
clack.note(renderProjectBox(projectName, options.projectRoot), "Project Setup");
|
|
139
|
+
|
|
140
|
+
const agentChoice = await clack.select({
|
|
141
|
+
message: "Which AI agent will you use?",
|
|
142
|
+
initialValue: parsedAi ?? detectedAi ?? "claude-cli",
|
|
143
|
+
options: [
|
|
144
|
+
{ value: "claude-cli", label: "Claude Code", hint: "Slash commands → .claude/commands/" },
|
|
145
|
+
{ value: "codex", label: "Codex", hint: "Skills → .agents/skills/" },
|
|
146
|
+
{ value: "gemini-cli", label: "Gemini CLI", hint: "Commands → .gemini/commands/" }
|
|
147
|
+
]
|
|
148
|
+
});
|
|
149
|
+
if (clack.isCancel(agentChoice)) {
|
|
150
|
+
clack.cancel("Initialization cancelled.");
|
|
151
|
+
throw new UserError("Initialization cancelled.");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const lang = await clack.select({
|
|
155
|
+
message: "Document language",
|
|
156
|
+
initialValue: defaultLang,
|
|
157
|
+
options: [
|
|
158
|
+
{ value: "en", label: "English" },
|
|
159
|
+
{ value: "tr", label: "Türkçe" }
|
|
160
|
+
]
|
|
161
|
+
});
|
|
162
|
+
if (clack.isCancel(lang)) {
|
|
163
|
+
clack.cancel("Initialization cancelled.");
|
|
164
|
+
throw new UserError("Initialization cancelled.");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const author = await clack.text({
|
|
168
|
+
message: "Author name",
|
|
169
|
+
placeholder: "Your name",
|
|
170
|
+
defaultValue: defaultAuthor
|
|
171
|
+
});
|
|
172
|
+
if (clack.isCancel(author)) {
|
|
173
|
+
clack.cancel("Initialization cancelled.");
|
|
174
|
+
throw new UserError("Initialization cancelled.");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
ai: agentChoice as SupportedAi,
|
|
179
|
+
script: fallbackScript,
|
|
180
|
+
lang: String(lang),
|
|
181
|
+
author: String(author).trim() || defaultAuthor,
|
|
182
|
+
interactive: true
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function finishInitInteractive(summary: {
|
|
187
|
+
projectRoot: string;
|
|
188
|
+
settingsPath: string;
|
|
189
|
+
ai?: SupportedAi;
|
|
190
|
+
lang: string;
|
|
191
|
+
author: string;
|
|
192
|
+
}): Promise<void> {
|
|
193
|
+
const agentLabel = summary.ai === "claude-cli" ? "Claude Code"
|
|
194
|
+
: summary.ai === "codex" ? "Codex"
|
|
195
|
+
: summary.ai === "gemini-cli" ? "Gemini CLI"
|
|
196
|
+
: "none";
|
|
197
|
+
|
|
198
|
+
const commandPrefix = summary.ai === "codex" ? "$" : "/";
|
|
199
|
+
const commands = [
|
|
200
|
+
`${commandPrefix}prodo-normalize`,
|
|
201
|
+
`${commandPrefix}prodo-prd`,
|
|
202
|
+
`${commandPrefix}prodo-workflow`,
|
|
203
|
+
`${commandPrefix}prodo-wireframe`,
|
|
204
|
+
`${commandPrefix}prodo-stories`,
|
|
205
|
+
`${commandPrefix}prodo-techspec`,
|
|
206
|
+
`${commandPrefix}prodo-validate`
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
return loadClack().then((clack) => clack.outro(
|
|
210
|
+
`Scaffold complete!\n\n` +
|
|
211
|
+
` Agent: ${agentLabel}\n` +
|
|
212
|
+
` Language: ${summary.lang}\n` +
|
|
213
|
+
` Author: ${summary.author}\n\n` +
|
|
214
|
+
`Next steps:\n` +
|
|
215
|
+
` 1. Edit brief.md with your product description\n` +
|
|
216
|
+
` 2. Open this folder in ${agentLabel}\n` +
|
|
217
|
+
` 3. Run commands in sequence:\n` +
|
|
218
|
+
` ${commands.join("\n ")}`
|
|
219
|
+
));
|
|
220
|
+
}
|
package/src/cli/init.ts
CHANGED
|
@@ -284,7 +284,7 @@ function summarizeParity(items: AssetManifestItem[]): ScaffoldManifest["parity_s
|
|
|
284
284
|
|
|
285
285
|
export async function runInit(
|
|
286
286
|
cwd: string,
|
|
287
|
-
options?: { ai?: SupportedAi; lang?: string; author?: string; preset?: string; script?: "sh" | "ps" }
|
|
287
|
+
options?: { ai?: SupportedAi; lang?: string; author?: string; preset?: string; script?: "sh" | "ps"; provider?: string }
|
|
288
288
|
): Promise<{ installedAgentFiles: string[]; settingsPath: string }> {
|
|
289
289
|
const root = prodoPath(cwd);
|
|
290
290
|
const artifactDefs = await listArtifactDefinitions(cwd);
|
|
@@ -392,7 +392,8 @@ export async function runInit(
|
|
|
392
392
|
const settingsPath = await writeSettings(cwd, {
|
|
393
393
|
lang: (options?.lang ?? "en").trim() || "en",
|
|
394
394
|
ai: options?.ai,
|
|
395
|
-
author: (options?.author ?? "").trim() || undefined
|
|
395
|
+
author: (options?.author ?? "").trim() || undefined,
|
|
396
|
+
provider: options?.provider
|
|
396
397
|
});
|
|
397
398
|
return { installedAgentFiles, settingsPath };
|
|
398
399
|
}
|
package/src/core/settings.ts
CHANGED
|
@@ -1,35 +1,41 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import { settingsPath } from "./paths";
|
|
3
|
-
import { fileExists } from "./utils";
|
|
4
|
-
|
|
5
|
-
export type ProdoSettings = {
|
|
6
|
-
lang: string;
|
|
7
|
-
ai?: string;
|
|
8
|
-
author?: string;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { settingsPath } from "./paths";
|
|
3
|
+
import { fileExists } from "./utils";
|
|
4
|
+
|
|
5
|
+
export type ProdoSettings = {
|
|
6
|
+
lang: string;
|
|
7
|
+
ai?: string;
|
|
8
|
+
author?: string;
|
|
9
|
+
provider?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const DEFAULT_SETTINGS: ProdoSettings = {
|
|
13
|
+
lang: "en"
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function readSettings(cwd: string): Promise<ProdoSettings> {
|
|
17
|
+
const path = settingsPath(cwd);
|
|
18
|
+
if (!(await fileExists(path))) return { ...DEFAULT_SETTINGS };
|
|
19
|
+
try {
|
|
20
|
+
const raw = await fs.readFile(path, "utf8");
|
|
21
|
+
const parsed = JSON.parse(raw) as Partial<ProdoSettings>;
|
|
22
|
+
return {
|
|
23
|
+
lang: typeof parsed.lang === "string" && parsed.lang.trim() ? parsed.lang.trim() : "en",
|
|
24
|
+
ai: typeof parsed.ai === "string" && parsed.ai.trim() ? parsed.ai.trim() : undefined,
|
|
25
|
+
author: typeof parsed.author === "string" && parsed.author.trim() ? parsed.author.trim() : undefined,
|
|
26
|
+
provider: typeof parsed.provider === "string" && parsed.provider.trim() ? parsed.provider.trim() : undefined
|
|
27
|
+
};
|
|
28
|
+
} catch {
|
|
29
|
+
return { ...DEFAULT_SETTINGS };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function writeSettings(cwd: string, settings: ProdoSettings): Promise<string> {
|
|
34
|
+
const path = settingsPath(cwd);
|
|
35
|
+
const clean: Record<string, unknown> = { lang: settings.lang };
|
|
36
|
+
if (settings.ai) clean.ai = settings.ai;
|
|
37
|
+
if (settings.author) clean.author = settings.author;
|
|
38
|
+
if (settings.provider) clean.provider = settings.provider;
|
|
39
|
+
await fs.writeFile(path, `${JSON.stringify(clean, null, 2)}\n`, "utf8");
|
|
40
|
+
return path;
|
|
41
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { normalizedBriefPath } from "../core/paths";
|
|
2
|
+
import { getActiveArtifactPath } from "../core/output-index";
|
|
3
|
+
import { fileExists } from "../core/utils";
|
|
4
|
+
import type { ArtifactType } from "../core/types";
|
|
5
|
+
import type { PipelineState } from "./types";
|
|
6
|
+
|
|
7
|
+
export function createPipelineState(cwd: string): PipelineState {
|
|
8
|
+
return {
|
|
9
|
+
cwd,
|
|
10
|
+
normalizedBriefPath: undefined,
|
|
11
|
+
generatedArtifacts: new Map(),
|
|
12
|
+
validationResult: undefined,
|
|
13
|
+
custom: {},
|
|
14
|
+
startedAt: new Date().toISOString(),
|
|
15
|
+
completedSkills: []
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function hydrateStateFromDisk(
|
|
20
|
+
cwd: string,
|
|
21
|
+
state: PipelineState,
|
|
22
|
+
artifactTypes: ArtifactType[]
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
const nbPath = normalizedBriefPath(cwd);
|
|
25
|
+
if (!state.normalizedBriefPath && (await fileExists(nbPath))) {
|
|
26
|
+
state.normalizedBriefPath = nbPath;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const type of artifactTypes) {
|
|
30
|
+
if (!state.generatedArtifacts.has(type)) {
|
|
31
|
+
const active = await getActiveArtifactPath(cwd, type);
|
|
32
|
+
if (active) {
|
|
33
|
+
state.generatedArtifacts.set(type, active);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isOutputSatisfied(
|
|
40
|
+
outputName: string,
|
|
41
|
+
state: PipelineState,
|
|
42
|
+
artifactType?: string
|
|
43
|
+
): boolean {
|
|
44
|
+
if (outputName === "normalizedBriefPath") {
|
|
45
|
+
return typeof state.normalizedBriefPath === "string" && state.normalizedBriefPath.length > 0;
|
|
46
|
+
}
|
|
47
|
+
if (outputName === "artifactPath" && artifactType) {
|
|
48
|
+
return state.generatedArtifacts.has(artifactType);
|
|
49
|
+
}
|
|
50
|
+
if (outputName === "validationResult") {
|
|
51
|
+
return state.validationResult !== undefined;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function wireInputsFromState(
|
|
57
|
+
inputNames: string[],
|
|
58
|
+
state: PipelineState
|
|
59
|
+
): Record<string, unknown> {
|
|
60
|
+
const inputs: Record<string, unknown> = {};
|
|
61
|
+
for (const name of inputNames) {
|
|
62
|
+
if (name === "cwd") inputs.cwd = state.cwd;
|
|
63
|
+
else if (name === "normalizedBriefPath") inputs.normalizedBriefPath = state.normalizedBriefPath;
|
|
64
|
+
else if (name in state.custom) inputs[name] = state.custom[name];
|
|
65
|
+
}
|
|
66
|
+
return inputs;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function wireOutputsToState(
|
|
70
|
+
outputs: Record<string, unknown>,
|
|
71
|
+
state: PipelineState,
|
|
72
|
+
skillName: string
|
|
73
|
+
): void {
|
|
74
|
+
if ("normalizedBriefPath" in outputs && typeof outputs.normalizedBriefPath === "string") {
|
|
75
|
+
state.normalizedBriefPath = outputs.normalizedBriefPath;
|
|
76
|
+
}
|
|
77
|
+
if ("artifactPath" in outputs && typeof outputs.artifactPath === "string") {
|
|
78
|
+
const artifactType = skillName;
|
|
79
|
+
state.generatedArtifacts.set(artifactType, outputs.artifactPath);
|
|
80
|
+
}
|
|
81
|
+
if ("validationResult" in outputs && outputs.validationResult && typeof outputs.validationResult === "object") {
|
|
82
|
+
state.validationResult = outputs.validationResult as PipelineState["validationResult"];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
86
|
+
if (!["normalizedBriefPath", "artifactPath", "validationResult"].includes(key)) {
|
|
87
|
+
state.custom[key] = value;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|