@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.
- 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 +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/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/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 +1 -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/package.json +74 -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} +459 -410
- package/src/{init-tui.ts → cli/init-tui.ts} +208 -208
- package/src/{init.ts → cli/init.ts} +398 -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} +35 -35
- 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/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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GoogleAgent = void 0;
|
|
4
|
+
const base_1 = require("../base");
|
|
5
|
+
const system_prompts_1 = require("../system-prompts");
|
|
6
|
+
const errors_1 = require("../../core/errors");
|
|
7
|
+
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
8
|
+
class GoogleAgent extends base_1.BaseAgent {
|
|
9
|
+
name = "google";
|
|
10
|
+
displayName = "Google Gemini";
|
|
11
|
+
sdkRequired = "@google/generative-ai";
|
|
12
|
+
getConfig() {
|
|
13
|
+
return {
|
|
14
|
+
name: this.name,
|
|
15
|
+
displayName: this.displayName,
|
|
16
|
+
sdkRequired: this.sdkRequired,
|
|
17
|
+
envVars: ["GOOGLE_API_KEY"],
|
|
18
|
+
defaultModel: "gemini-2.0-flash"
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async generate(prompt, inputContext, schemaHint) {
|
|
22
|
+
const apiKey = process.env.GOOGLE_API_KEY;
|
|
23
|
+
if (!apiKey) {
|
|
24
|
+
throw new errors_1.UserError("GOOGLE_API_KEY is not set. Set it to use the Google Gemini agent.");
|
|
25
|
+
}
|
|
26
|
+
const model = process.env.PRODO_GOOGLE_MODEL ?? "gemini-2.0-flash";
|
|
27
|
+
const outputLanguage = typeof inputContext.outputLanguage === "string" && inputContext.outputLanguage.trim()
|
|
28
|
+
? inputContext.outputLanguage.trim()
|
|
29
|
+
: "en";
|
|
30
|
+
const system = (0, system_prompts_1.buildSystemPrompt)(schemaHint, outputLanguage);
|
|
31
|
+
const user = (0, system_prompts_1.buildUserMessage)(prompt, inputContext);
|
|
32
|
+
let GoogleGenerativeAI;
|
|
33
|
+
try {
|
|
34
|
+
const mod = (await dynamicImport("@google/generative-ai"));
|
|
35
|
+
GoogleGenerativeAI = mod.GoogleGenerativeAI;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new errors_1.UserError("Google Generative AI SDK is not installed. Run: npm install @google/generative-ai");
|
|
39
|
+
}
|
|
40
|
+
const client = new GoogleGenerativeAI(apiKey);
|
|
41
|
+
const generativeModel = client.getGenerativeModel({
|
|
42
|
+
model,
|
|
43
|
+
systemInstruction: system
|
|
44
|
+
});
|
|
45
|
+
const result = await generativeModel.generateContent(user);
|
|
46
|
+
const content = result.response.text().trim();
|
|
47
|
+
if (!content) {
|
|
48
|
+
throw new errors_1.UserError("Google Gemini agent returned an empty response.");
|
|
49
|
+
}
|
|
50
|
+
return { body: content };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.GoogleAgent = GoogleAgent;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseAgent, type AgentConfig } from "../base";
|
|
2
|
+
import type { GenerateResult, ProviderSchemaHint } from "../../core/types";
|
|
3
|
+
export declare class MockAgent extends BaseAgent {
|
|
4
|
+
readonly name = "mock";
|
|
5
|
+
readonly displayName = "Mock (Testing)";
|
|
6
|
+
readonly sdkRequired: null;
|
|
7
|
+
private readonly provider;
|
|
8
|
+
generate(prompt: string, inputContext: Record<string, unknown>, schemaHint: ProviderSchemaHint): Promise<GenerateResult>;
|
|
9
|
+
isAvailable(): Promise<boolean>;
|
|
10
|
+
getConfig(): AgentConfig;
|
|
11
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MockAgent = void 0;
|
|
4
|
+
const base_1 = require("../base");
|
|
5
|
+
const mock_provider_1 = require("../../providers/mock-provider");
|
|
6
|
+
class MockAgent extends base_1.BaseAgent {
|
|
7
|
+
name = "mock";
|
|
8
|
+
displayName = "Mock (Testing)";
|
|
9
|
+
sdkRequired = null;
|
|
10
|
+
provider = new mock_provider_1.MockProvider();
|
|
11
|
+
async generate(prompt, inputContext, schemaHint) {
|
|
12
|
+
return this.provider.generate(prompt, inputContext, schemaHint);
|
|
13
|
+
}
|
|
14
|
+
async isAvailable() {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
getConfig() {
|
|
18
|
+
return {
|
|
19
|
+
name: this.name,
|
|
20
|
+
displayName: this.displayName,
|
|
21
|
+
sdkRequired: null,
|
|
22
|
+
envVars: []
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.MockAgent = MockAgent;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseAgent, type AgentConfig } from "../base";
|
|
2
|
+
import type { GenerateResult, ProviderSchemaHint } from "../../core/types";
|
|
3
|
+
export declare class OpenAIAgent extends BaseAgent {
|
|
4
|
+
readonly name = "openai";
|
|
5
|
+
readonly displayName = "OpenAI";
|
|
6
|
+
readonly sdkRequired = "openai";
|
|
7
|
+
getConfig(): AgentConfig;
|
|
8
|
+
generate(prompt: string, inputContext: Record<string, unknown>, schemaHint: ProviderSchemaHint): Promise<GenerateResult>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenAIAgent = void 0;
|
|
4
|
+
const base_1 = require("../base");
|
|
5
|
+
const system_prompts_1 = require("../system-prompts");
|
|
6
|
+
const errors_1 = require("../../core/errors");
|
|
7
|
+
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
8
|
+
class OpenAIAgent extends base_1.BaseAgent {
|
|
9
|
+
name = "openai";
|
|
10
|
+
displayName = "OpenAI";
|
|
11
|
+
sdkRequired = "openai";
|
|
12
|
+
getConfig() {
|
|
13
|
+
return {
|
|
14
|
+
name: this.name,
|
|
15
|
+
displayName: this.displayName,
|
|
16
|
+
sdkRequired: this.sdkRequired,
|
|
17
|
+
envVars: ["OPENAI_API_KEY"],
|
|
18
|
+
defaultModel: "gpt-4o-mini"
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async generate(prompt, inputContext, schemaHint) {
|
|
22
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
23
|
+
if (!apiKey) {
|
|
24
|
+
throw new errors_1.UserError("OPENAI_API_KEY is not set. Set it to use the OpenAI agent.");
|
|
25
|
+
}
|
|
26
|
+
const model = process.env.PRODO_OPENAI_MODEL ?? "gpt-4o-mini";
|
|
27
|
+
const baseURL = process.env.PRODO_OPENAI_BASE_URL ?? undefined;
|
|
28
|
+
const outputLanguage = typeof inputContext.outputLanguage === "string" && inputContext.outputLanguage.trim()
|
|
29
|
+
? inputContext.outputLanguage.trim()
|
|
30
|
+
: "en";
|
|
31
|
+
const system = (0, system_prompts_1.buildSystemPrompt)(schemaHint, outputLanguage);
|
|
32
|
+
const user = (0, system_prompts_1.buildUserMessage)(prompt, inputContext);
|
|
33
|
+
let OpenAIConstructor;
|
|
34
|
+
try {
|
|
35
|
+
const mod = (await dynamicImport("openai"));
|
|
36
|
+
OpenAIConstructor = mod.default;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new errors_1.UserError("OpenAI SDK is not installed. Run: npm install openai");
|
|
40
|
+
}
|
|
41
|
+
const client = new OpenAIConstructor({ apiKey, baseURL });
|
|
42
|
+
const response = await client.chat.completions.create({
|
|
43
|
+
model,
|
|
44
|
+
messages: [
|
|
45
|
+
{ role: "system", content: system },
|
|
46
|
+
{ role: "user", content: user }
|
|
47
|
+
],
|
|
48
|
+
temperature: 0.2
|
|
49
|
+
});
|
|
50
|
+
const content = response.choices[0]?.message?.content?.trim();
|
|
51
|
+
if (!content) {
|
|
52
|
+
throw new errors_1.UserError("OpenAI agent returned an empty response.");
|
|
53
|
+
}
|
|
54
|
+
return { body: content };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.OpenAIAgent = OpenAIAgent;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildSystemPrompt = buildSystemPrompt;
|
|
4
|
+
exports.buildUserMessage = buildUserMessage;
|
|
5
|
+
function buildSystemPrompt(schemaHint, outputLanguage) {
|
|
6
|
+
const mode = schemaHint.artifactType;
|
|
7
|
+
if (mode === "normalize") {
|
|
8
|
+
return `You normalize messy human product briefs into strict JSON.
|
|
9
|
+
Return valid JSON only, no markdown. Include confidence scores (0..1) for critical fields.
|
|
10
|
+
Preserve source language and Unicode characters exactly; never transliterate Turkish letters to ASCII.`;
|
|
11
|
+
}
|
|
12
|
+
if (mode === "semantic_consistency") {
|
|
13
|
+
return `You detect semantic inconsistencies between paired artifacts.
|
|
14
|
+
Return valid JSON only: { "issues": [{level, code, check, contract_id, file, message, suggestion}] }.`;
|
|
15
|
+
}
|
|
16
|
+
if (mode === "contract_relevance") {
|
|
17
|
+
return `You verify whether tagged content actually matches the referenced contract text.
|
|
18
|
+
Return valid JSON only: { "relevant": boolean, "score": number, "reason": string }.`;
|
|
19
|
+
}
|
|
20
|
+
return `You are a product-document generator.
|
|
21
|
+
Return only Markdown body content.
|
|
22
|
+
Headings required:
|
|
23
|
+
${schemaHint.requiredHeadings.join("\n")}
|
|
24
|
+
Required contract tags:
|
|
25
|
+
${schemaHint.requiredContracts.join(", ")}
|
|
26
|
+
Use tags like [G1], [F2], [C1] where relevant.
|
|
27
|
+
Output language: ${outputLanguage}
|
|
28
|
+
Do not translate required headings.`;
|
|
29
|
+
}
|
|
30
|
+
function buildUserMessage(prompt, inputContext) {
|
|
31
|
+
return `${prompt}\n\nContext JSON:\n${JSON.stringify(inputContext, null, 2)}`;
|
|
32
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const AI_ALIASES: Record<string, "codex" | "gemini-cli" | "claude-cli">;
|
|
2
|
+
export type SupportedAi = "codex" | "gemini-cli" | "claude-cli";
|
|
3
|
+
export declare function resolveAi(ai?: string): SupportedAi | undefined;
|
|
4
|
+
export declare function installAgentCommands(projectRoot: string, ai: SupportedAi): Promise<string[]>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AI_ALIASES = void 0;
|
|
7
|
+
exports.resolveAi = resolveAi;
|
|
8
|
+
exports.installAgentCommands = installAgentCommands;
|
|
9
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
12
|
+
const errors_1 = require("../core/errors");
|
|
13
|
+
const utils_1 = require("../core/utils");
|
|
14
|
+
exports.AI_ALIASES = {
|
|
15
|
+
codex: "codex",
|
|
16
|
+
gemini: "gemini-cli",
|
|
17
|
+
"gemmini-cli": "gemini-cli",
|
|
18
|
+
"gemmini": "gemini-cli",
|
|
19
|
+
"gemini-cli": "gemini-cli",
|
|
20
|
+
claude: "claude-cli",
|
|
21
|
+
"claude-cli": "claude-cli"
|
|
22
|
+
};
|
|
23
|
+
const AGENT_CONFIG = {
|
|
24
|
+
"claude-cli": {
|
|
25
|
+
baseDir: ".claude/commands",
|
|
26
|
+
format: "markdown",
|
|
27
|
+
extension: ".md",
|
|
28
|
+
argsPlaceholder: "$ARGUMENTS"
|
|
29
|
+
},
|
|
30
|
+
"gemini-cli": {
|
|
31
|
+
baseDir: ".gemini/commands",
|
|
32
|
+
format: "toml",
|
|
33
|
+
extension: ".toml",
|
|
34
|
+
argsPlaceholder: "{{args}}"
|
|
35
|
+
},
|
|
36
|
+
codex: {
|
|
37
|
+
baseDir: ".agents/skills",
|
|
38
|
+
format: "skill",
|
|
39
|
+
extension: "/SKILL.md",
|
|
40
|
+
argsPlaceholder: "$ARGUMENTS"
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
function parseFrontmatter(content) {
|
|
44
|
+
if (!content.startsWith("---"))
|
|
45
|
+
return { frontmatter: {}, body: content };
|
|
46
|
+
const end = content.indexOf("\n---", 4);
|
|
47
|
+
if (end === -1)
|
|
48
|
+
return { frontmatter: {}, body: content };
|
|
49
|
+
const fmRaw = content.slice(3, end).trim();
|
|
50
|
+
const body = content.slice(end + 4).trimStart();
|
|
51
|
+
const frontmatter = js_yaml_1.default.load(fmRaw) ?? {};
|
|
52
|
+
return { frontmatter, body };
|
|
53
|
+
}
|
|
54
|
+
function renderFrontmatter(frontmatter) {
|
|
55
|
+
if (Object.keys(frontmatter).length === 0)
|
|
56
|
+
return "";
|
|
57
|
+
return `---\n${js_yaml_1.default.dump(frontmatter)}---\n`;
|
|
58
|
+
}
|
|
59
|
+
function sanitizeFrontmatter(frontmatter) {
|
|
60
|
+
const out = { ...frontmatter };
|
|
61
|
+
delete out.run;
|
|
62
|
+
delete out.scripts;
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
function toTomlPrompt(body, frontmatter, argsPlaceholder) {
|
|
66
|
+
const description = String(frontmatter.description ?? "Prodo command");
|
|
67
|
+
const promptBody = body.replaceAll("$ARGUMENTS", argsPlaceholder);
|
|
68
|
+
return `description = "${description.replace(/"/g, '\\"')}"
|
|
69
|
+
|
|
70
|
+
prompt = """
|
|
71
|
+
Important execution rule:
|
|
72
|
+
- This is an agent slash command, not a shell command.
|
|
73
|
+
- Do NOT run \`prodo-normalize\`, \`prodo-prd\`, or \`prodo ...\` in shell.
|
|
74
|
+
- Execute the workflow directly using workspace files.
|
|
75
|
+
|
|
76
|
+
${promptBody}
|
|
77
|
+
"""`;
|
|
78
|
+
}
|
|
79
|
+
function toSkill(name, body, frontmatter) {
|
|
80
|
+
const description = String(frontmatter.description ?? "Prodo workflow command");
|
|
81
|
+
return `---
|
|
82
|
+
name: ${name}
|
|
83
|
+
description: ${description}
|
|
84
|
+
compatibility: Requires Prodo project scaffold (.prodo)
|
|
85
|
+
metadata:
|
|
86
|
+
author: prodo
|
|
87
|
+
source: .prodo/commands/${name}.md
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
${body}`;
|
|
91
|
+
}
|
|
92
|
+
function resolveAi(ai) {
|
|
93
|
+
if (!ai)
|
|
94
|
+
return undefined;
|
|
95
|
+
const normalized = exports.AI_ALIASES[ai.trim().toLowerCase()];
|
|
96
|
+
if (!normalized) {
|
|
97
|
+
throw new errors_1.UserError("Unsupported --ai value. Use: codex | gemini-cli | claude-cli");
|
|
98
|
+
}
|
|
99
|
+
return normalized;
|
|
100
|
+
}
|
|
101
|
+
async function loadCommandTemplateNames(commandTemplatesDir) {
|
|
102
|
+
if (!(await (0, utils_1.fileExists)(commandTemplatesDir))) {
|
|
103
|
+
throw new errors_1.UserError(`Missing command templates directory: ${commandTemplatesDir}`);
|
|
104
|
+
}
|
|
105
|
+
const entries = await promises_1.default.readdir(commandTemplatesDir, { withFileTypes: true });
|
|
106
|
+
return entries
|
|
107
|
+
.filter((entry) => entry.isFile())
|
|
108
|
+
.map((entry) => entry.name)
|
|
109
|
+
.filter((name) => name.endsWith(".md") && name.startsWith("prodo-"))
|
|
110
|
+
.map((name) => name.replace(/\.md$/, ""))
|
|
111
|
+
.sort();
|
|
112
|
+
}
|
|
113
|
+
async function installAgentCommands(projectRoot, ai) {
|
|
114
|
+
const cfg = AGENT_CONFIG[ai];
|
|
115
|
+
const target = node_path_1.default.join(projectRoot, cfg.baseDir);
|
|
116
|
+
const commandTemplatesDir = node_path_1.default.join(projectRoot, ".prodo", "commands");
|
|
117
|
+
const commandNames = await loadCommandTemplateNames(commandTemplatesDir);
|
|
118
|
+
await (0, utils_1.ensureDir)(target);
|
|
119
|
+
const written = [];
|
|
120
|
+
for (const commandName of commandNames) {
|
|
121
|
+
const templatePath = node_path_1.default.join(commandTemplatesDir, `${commandName}.md`);
|
|
122
|
+
if (!(await (0, utils_1.fileExists)(templatePath))) {
|
|
123
|
+
throw new errors_1.UserError(`Missing command template: ${templatePath}`);
|
|
124
|
+
}
|
|
125
|
+
const raw = await promises_1.default.readFile(templatePath, "utf8");
|
|
126
|
+
const parsed = parseFrontmatter(raw);
|
|
127
|
+
if (cfg.format === "skill") {
|
|
128
|
+
const skillDir = node_path_1.default.join(target, commandName);
|
|
129
|
+
await (0, utils_1.ensureDir)(skillDir);
|
|
130
|
+
const outPath = node_path_1.default.join(skillDir, "SKILL.md");
|
|
131
|
+
const content = toSkill(commandName, parsed.body.replaceAll("$ARGUMENTS", cfg.argsPlaceholder), parsed.frontmatter);
|
|
132
|
+
await promises_1.default.writeFile(outPath, content, "utf8");
|
|
133
|
+
written.push(outPath);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (cfg.format === "toml") {
|
|
137
|
+
const outPath = node_path_1.default.join(target, `${commandName}${cfg.extension}`);
|
|
138
|
+
await promises_1.default.writeFile(outPath, toTomlPrompt(parsed.body, parsed.frontmatter, cfg.argsPlaceholder), "utf8");
|
|
139
|
+
written.push(outPath);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const outPath = node_path_1.default.join(target, `${commandName}${cfg.extension}`);
|
|
143
|
+
const replacedBody = parsed.body.replaceAll("$ARGUMENTS", cfg.argsPlaceholder);
|
|
144
|
+
await promises_1.default.writeFile(outPath, `${renderFrontmatter(sanitizeFrontmatter(parsed.frontmatter))}\n${replacedBody}`, "utf8");
|
|
145
|
+
written.push(outPath);
|
|
146
|
+
}
|
|
147
|
+
return written;
|
|
148
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const AGENT_IDS: readonly ["codex", "gemini-cli", "claude-cli"];
|
|
2
|
+
export type AgentId = (typeof AGENT_IDS)[number];
|
|
3
|
+
export declare function isSupportedAgent(agent?: string): agent is AgentId;
|
|
4
|
+
export declare function resolveAgent(agent?: string): AgentId | undefined;
|
|
5
|
+
export type CommandSetItem = {
|
|
6
|
+
command: string;
|
|
7
|
+
purpose: string;
|
|
8
|
+
};
|
|
9
|
+
export type AgentCommandSet = {
|
|
10
|
+
agent: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
recommended_sequence?: CommandSetItem[];
|
|
13
|
+
artifact_shortcuts?: Record<string, string>;
|
|
14
|
+
};
|
|
15
|
+
export declare function loadAgentCommandSet(_cwd: string, agent: AgentId): Promise<AgentCommandSet>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AGENT_IDS = void 0;
|
|
4
|
+
exports.isSupportedAgent = isSupportedAgent;
|
|
5
|
+
exports.resolveAgent = resolveAgent;
|
|
6
|
+
exports.loadAgentCommandSet = loadAgentCommandSet;
|
|
7
|
+
const errors_1 = require("../core/errors");
|
|
8
|
+
exports.AGENT_IDS = ["codex", "gemini-cli", "claude-cli"];
|
|
9
|
+
function isSupportedAgent(agent) {
|
|
10
|
+
if (!agent)
|
|
11
|
+
return false;
|
|
12
|
+
return exports.AGENT_IDS.includes(agent);
|
|
13
|
+
}
|
|
14
|
+
function resolveAgent(agent) {
|
|
15
|
+
if (!agent)
|
|
16
|
+
return undefined;
|
|
17
|
+
if (!isSupportedAgent(agent)) {
|
|
18
|
+
throw new errors_1.UserError(`Unsupported agent: ${agent}. Supported: ${exports.AGENT_IDS.join(", ")}`);
|
|
19
|
+
}
|
|
20
|
+
return agent;
|
|
21
|
+
}
|
|
22
|
+
async function loadAgentCommandSet(_cwd, agent) {
|
|
23
|
+
const prefix = "/prodo";
|
|
24
|
+
return {
|
|
25
|
+
agent,
|
|
26
|
+
description: "Agent-specific command set for Prodo artifact pipeline.",
|
|
27
|
+
recommended_sequence: [
|
|
28
|
+
{ command: "prodo init . --ai <agent> --lang <en|tr>", purpose: "Initialize Prodo scaffold and agent commands." },
|
|
29
|
+
{ command: `${prefix}-normalize`, purpose: "Normalize start brief into normalized brief JSON." },
|
|
30
|
+
{ command: `${prefix}-prd`, purpose: "Generate PRD artifact." },
|
|
31
|
+
{ command: `${prefix}-workflow`, purpose: "Generate workflow artifact." },
|
|
32
|
+
{ command: `${prefix}-wireframe`, purpose: "Generate wireframe artifact." },
|
|
33
|
+
{ command: `${prefix}-stories`, purpose: "Generate stories artifact." },
|
|
34
|
+
{ command: `${prefix}-techspec`, purpose: "Generate techspec artifact." },
|
|
35
|
+
{ command: `${prefix}-validate`, purpose: "Run validation report." },
|
|
36
|
+
{ command: `${prefix}-fix`, purpose: "Fix artifacts when validation fails." }
|
|
37
|
+
],
|
|
38
|
+
artifact_shortcuts: {
|
|
39
|
+
normalize: `${prefix}-normalize`,
|
|
40
|
+
prd: `${prefix}-prd`,
|
|
41
|
+
workflow: `${prefix}-workflow`,
|
|
42
|
+
wireframe: `${prefix}-wireframe`,
|
|
43
|
+
stories: `${prefix}-stories`,
|
|
44
|
+
techspec: `${prefix}-techspec`,
|
|
45
|
+
validate: `${prefix}-validate`,
|
|
46
|
+
fix: `${prefix}-fix`
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDoctor(cwd: string, out: (line: string) => void): Promise<void>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runDoctor = runDoctor;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const agent_registry_1 = require("../agents/agent-registry");
|
|
6
|
+
const paths_1 = require("../core/paths");
|
|
7
|
+
const utils_1 = require("../core/utils");
|
|
8
|
+
const version_1 = require("../core/version");
|
|
9
|
+
function termWidth() {
|
|
10
|
+
return Math.max(80, process.stdout.columns ?? 100);
|
|
11
|
+
}
|
|
12
|
+
function stripAnsi(input) {
|
|
13
|
+
return input.replace(/\u001B\[[0-9;]*m/g, "");
|
|
14
|
+
}
|
|
15
|
+
function color(input, code) {
|
|
16
|
+
if (!process.stdout.isTTY)
|
|
17
|
+
return input;
|
|
18
|
+
return `${code}${input}\u001B[0m`;
|
|
19
|
+
}
|
|
20
|
+
function center(input, width) {
|
|
21
|
+
const visible = stripAnsi(input).length;
|
|
22
|
+
const left = Math.max(0, Math.floor((width - visible) / 2));
|
|
23
|
+
return `${" ".repeat(left)}${input}`;
|
|
24
|
+
}
|
|
25
|
+
function iconFor(status) {
|
|
26
|
+
if (status === "available")
|
|
27
|
+
return color("✔", "\u001B[32m");
|
|
28
|
+
if (status === "not_found")
|
|
29
|
+
return color("✖", "\u001B[31m");
|
|
30
|
+
return color("•", "\u001B[33m");
|
|
31
|
+
}
|
|
32
|
+
function labelFor(status) {
|
|
33
|
+
if (status === "available")
|
|
34
|
+
return color("available", "\u001B[32m");
|
|
35
|
+
if (status === "not_found")
|
|
36
|
+
return color("not found", "\u001B[31m");
|
|
37
|
+
return color("IDE-based", "\u001B[2;33m");
|
|
38
|
+
}
|
|
39
|
+
function renderRows(rows) {
|
|
40
|
+
const leftWidth = Math.max(...rows.map((row) => row.name.length), 10);
|
|
41
|
+
return rows.map((row) => {
|
|
42
|
+
const left = row.name.padEnd(leftWidth, " ");
|
|
43
|
+
const status = `${labelFor(row.status)}${row.detail ? ` (${row.detail})` : ""}`;
|
|
44
|
+
return ` ${iconFor(row.status)} ${left} ${status}`;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function commandExists(command) {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
const lookup = process.platform === "win32" ? "where" : "which";
|
|
50
|
+
const child = (0, node_child_process_1.spawn)(lookup, [command], { stdio: "ignore" });
|
|
51
|
+
child.on("error", () => resolve(false));
|
|
52
|
+
child.on("close", (code) => resolve(code === 0));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async function firstAvailable(commands) {
|
|
56
|
+
for (const command of commands) {
|
|
57
|
+
if (await commandExists(command))
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
function renderLogo(width) {
|
|
63
|
+
const cyan = "\u001B[38;5;45m";
|
|
64
|
+
const blue = "\u001B[38;5;39m";
|
|
65
|
+
const logo = [
|
|
66
|
+
"██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ",
|
|
67
|
+
"██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██╔═══██╗",
|
|
68
|
+
"██████╔╝██████╔╝██║ ██║██║ ██║██║ ██║",
|
|
69
|
+
"██╔═══╝ ██╔══██╗██║ ██║██║ ██║██║ ██║",
|
|
70
|
+
"██║ ██║ ██║╚██████╔╝██████╔╝╚██████╔╝"
|
|
71
|
+
];
|
|
72
|
+
const painted = logo.map((line, idx) => color(line, idx % 2 === 0 ? cyan : blue));
|
|
73
|
+
return painted.map((line) => center(line, width)).join("\n");
|
|
74
|
+
}
|
|
75
|
+
async function runDoctor(cwd, out) {
|
|
76
|
+
const width = termWidth();
|
|
77
|
+
const version = await (0, version_1.readCliVersion)(cwd);
|
|
78
|
+
const isInitialized = await (0, utils_1.fileExists)((0, paths_1.prodoPath)(cwd));
|
|
79
|
+
const codex = await firstAvailable(["codex"]);
|
|
80
|
+
const gemini = await firstAvailable(["gemini", "gemini-cli"]);
|
|
81
|
+
const claude = await firstAvailable(["claude", "claude-cli"]);
|
|
82
|
+
const git = await firstAvailable(["git"]);
|
|
83
|
+
const node = await firstAvailable(["node"]);
|
|
84
|
+
const vscode = await firstAvailable(["code"]);
|
|
85
|
+
const coreRows = [
|
|
86
|
+
{ name: "Prodo CLI", status: "available", detail: `v${version}` },
|
|
87
|
+
{
|
|
88
|
+
name: "Project initialized",
|
|
89
|
+
status: isInitialized ? "available" : "not_found",
|
|
90
|
+
detail: isInitialized ? ".prodo found" : ".prodo missing"
|
|
91
|
+
}
|
|
92
|
+
];
|
|
93
|
+
const aiCliRows = [
|
|
94
|
+
{ name: "Codex CLI", status: codex ? "available" : "not_found", detail: codex ? "available" : "not found" },
|
|
95
|
+
{ name: "Gemini CLI", status: gemini ? "available" : "not_found", detail: gemini ? "available" : "not found" },
|
|
96
|
+
{ name: "Claude CLI", status: claude ? "available" : "not_found", detail: claude ? "available" : "not found" }
|
|
97
|
+
];
|
|
98
|
+
const registry = (0, agent_registry_1.getGlobalRegistry)();
|
|
99
|
+
const agentRows = [];
|
|
100
|
+
for (const agent of registry.list()) {
|
|
101
|
+
if (agent.name === "mock")
|
|
102
|
+
continue;
|
|
103
|
+
const available = await agent.isAvailable();
|
|
104
|
+
const config = agent.getConfig();
|
|
105
|
+
const sdkLabel = config.sdkRequired ? `SDK: ${config.sdkRequired}` : "";
|
|
106
|
+
const envLabel = config.envVars.filter((v) => !process.env[v]).map((v) => `${v} missing`).join(", ");
|
|
107
|
+
const detail = available ? "ready" : [sdkLabel, envLabel].filter(Boolean).join(", ") || "not configured";
|
|
108
|
+
agentRows.push({
|
|
109
|
+
name: config.displayName,
|
|
110
|
+
status: available ? "available" : "not_found",
|
|
111
|
+
detail
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const devRows = [
|
|
115
|
+
{ name: "Git", status: git ? "available" : "not_found", detail: git ? "available" : "not found" },
|
|
116
|
+
{ name: "Node.js", status: node ? "available" : "not_found", detail: node ? "available" : "not found" },
|
|
117
|
+
{ name: "Visual Studio Code", status: vscode ? "available" : "not_found", detail: vscode ? "available" : "not found" },
|
|
118
|
+
{ name: "Cursor", status: "ide", detail: "IDE-based, no CLI check" }
|
|
119
|
+
];
|
|
120
|
+
out("");
|
|
121
|
+
out(renderLogo(width));
|
|
122
|
+
out("");
|
|
123
|
+
out(center(color("Prodo — Product Artifact Toolkit", "\u001B[1;37m"), width));
|
|
124
|
+
out(center(color("Crafted by Codex, guided by Shahmarasy intelligence", "\u001B[2;37m"), width));
|
|
125
|
+
out("");
|
|
126
|
+
out("Checking environment...");
|
|
127
|
+
out("");
|
|
128
|
+
out(color("Core", "\u001B[1m"));
|
|
129
|
+
for (const line of renderRows(coreRows))
|
|
130
|
+
out(line);
|
|
131
|
+
out("");
|
|
132
|
+
out(color("AI CLI Tools", "\u001B[1m"));
|
|
133
|
+
for (const line of renderRows(aiCliRows))
|
|
134
|
+
out(line);
|
|
135
|
+
out("");
|
|
136
|
+
out(color("LLM Agents (SDK)", "\u001B[1m"));
|
|
137
|
+
for (const line of renderRows(agentRows))
|
|
138
|
+
out(line);
|
|
139
|
+
out("");
|
|
140
|
+
out(color("Dev Tools", "\u001B[1m"));
|
|
141
|
+
for (const line of renderRows(devRows))
|
|
142
|
+
out(line);
|
|
143
|
+
out("");
|
|
144
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FixProposal, FixResult } from "../core/fix";
|
|
2
|
+
export declare function displayFixProposal(proposal: FixProposal, log: (message: string) => void): Promise<void>;
|
|
3
|
+
export declare function confirmFixExecution(proposal: FixProposal): Promise<boolean>;
|
|
4
|
+
export declare function displayFixResult(result: FixResult, log: (message: string) => void): Promise<void>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.displayFixProposal = displayFixProposal;
|
|
4
|
+
exports.confirmFixExecution = confirmFixExecution;
|
|
5
|
+
exports.displayFixResult = displayFixResult;
|
|
6
|
+
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
7
|
+
async function loadClack() {
|
|
8
|
+
try {
|
|
9
|
+
return (await dynamicImport("@clack/prompts"));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function displayFixProposal(proposal, log) {
|
|
16
|
+
const errorCount = proposal.issues.filter((i) => i.level === "error").length;
|
|
17
|
+
const warningCount = proposal.issues.filter((i) => i.level === "warning").length;
|
|
18
|
+
log("");
|
|
19
|
+
log(`Fix Proposal`);
|
|
20
|
+
log(`${"─".repeat(50)}`);
|
|
21
|
+
log(`Issues found: ${errorCount} error(s), ${warningCount} warning(s)`);
|
|
22
|
+
log(`Artifacts to regenerate: ${proposal.targets.join(", ")}`);
|
|
23
|
+
log("");
|
|
24
|
+
for (const [type, issues] of proposal.issuesByArtifact) {
|
|
25
|
+
log(` ${type.toUpperCase()} (${issues.length} issue${issues.length > 1 ? "s" : ""}):`);
|
|
26
|
+
for (const issue of issues) {
|
|
27
|
+
const prefix = issue.level === "error" ? " ✖" : " ⚠";
|
|
28
|
+
log(` ${prefix} [${issue.code}] ${issue.message}`);
|
|
29
|
+
if (issue.suggestion) {
|
|
30
|
+
log(` → ${issue.suggestion}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
log("");
|
|
34
|
+
}
|
|
35
|
+
const orphanIssues = proposal.issues.filter((i) => !i.artifactType);
|
|
36
|
+
if (orphanIssues.length > 0) {
|
|
37
|
+
log(` General (${orphanIssues.length} issue${orphanIssues.length > 1 ? "s" : ""}):`);
|
|
38
|
+
for (const issue of orphanIssues) {
|
|
39
|
+
const prefix = issue.level === "error" ? " ✖" : " ⚠";
|
|
40
|
+
log(` ${prefix} [${issue.code}] ${issue.message}`);
|
|
41
|
+
}
|
|
42
|
+
log("");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function confirmFixExecution(proposal) {
|
|
46
|
+
if (process.stdout.isTTY !== true) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
const clack = await loadClack();
|
|
50
|
+
if (!clack) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
clack.intro("Prodo Fix");
|
|
54
|
+
const proceed = await clack.confirm({
|
|
55
|
+
message: `Regenerate ${proposal.targets.length} artifact(s): ${proposal.targets.join(", ")}?`
|
|
56
|
+
});
|
|
57
|
+
if (clack.isCancel(proceed)) {
|
|
58
|
+
clack.cancel("Fix cancelled.");
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return proceed === true;
|
|
62
|
+
}
|
|
63
|
+
async function displayFixResult(result, log) {
|
|
64
|
+
log("");
|
|
65
|
+
if (result.finalPass) {
|
|
66
|
+
log("Fix complete — validation passed.");
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
log("Fix applied but validation still failing.");
|
|
70
|
+
log(`Review report: ${result.reportPath}`);
|
|
71
|
+
if (result.backupDir) {
|
|
72
|
+
log(`Backup saved: ${result.backupDir}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const clack = await loadClack();
|
|
76
|
+
if (clack && process.stdout.isTTY) {
|
|
77
|
+
clack.outro(result.finalPass ? "All gates passed!" : "Review report and retry.");
|
|
78
|
+
}
|
|
79
|
+
}
|