@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
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
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.runInit = runInit;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_crypto_1 = require("node:crypto");
|
|
10
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
11
|
+
const agent_command_installer_1 = require("./agent-command-installer");
|
|
12
|
+
const artifact_registry_1 = require("../core/artifact-registry");
|
|
13
|
+
const utils_1 = require("../core/utils");
|
|
14
|
+
const paths_1 = require("../core/paths");
|
|
15
|
+
const preset_loader_1 = require("./preset-loader");
|
|
16
|
+
const registry_1 = require("../core/registry");
|
|
17
|
+
const settings_1 = require("../core/settings");
|
|
18
|
+
const template_resolver_1 = require("../core/template-resolver");
|
|
19
|
+
const workflow_commands_1 = require("../core/workflow-commands");
|
|
20
|
+
const templates_1 = require("../core/templates");
|
|
21
|
+
function templateFileName(artifactType) {
|
|
22
|
+
if (artifactType === "workflow")
|
|
23
|
+
return `${artifactType}.mmd`;
|
|
24
|
+
if (artifactType === "wireframe")
|
|
25
|
+
return `${artifactType}.html`;
|
|
26
|
+
return `${artifactType}.md`;
|
|
27
|
+
}
|
|
28
|
+
async function writeFileIfMissing(filePath, content) {
|
|
29
|
+
if (await (0, utils_1.fileExists)(filePath))
|
|
30
|
+
return;
|
|
31
|
+
await promises_1.default.writeFile(filePath, content, "utf8");
|
|
32
|
+
}
|
|
33
|
+
async function readProdoVersion(cwd) {
|
|
34
|
+
const candidates = [
|
|
35
|
+
node_path_1.default.join(cwd, "package.json"),
|
|
36
|
+
node_path_1.default.resolve(__dirname, "..", "..", "package.json")
|
|
37
|
+
];
|
|
38
|
+
for (const candidate of candidates) {
|
|
39
|
+
if (!(await (0, utils_1.fileExists)(candidate)))
|
|
40
|
+
continue;
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(await promises_1.default.readFile(candidate, "utf8"));
|
|
43
|
+
if (typeof parsed.version === "string" && parsed.version.trim().length > 0)
|
|
44
|
+
return parsed.version;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// ignore and continue
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return "0.0.0-dev";
|
|
51
|
+
}
|
|
52
|
+
async function fileSha256(filePath) {
|
|
53
|
+
const content = await promises_1.default.readFile(filePath);
|
|
54
|
+
return (0, node_crypto_1.createHash)("sha256").update(content).digest("hex");
|
|
55
|
+
}
|
|
56
|
+
async function listFilesRecursive(rootDir) {
|
|
57
|
+
if (!(await (0, utils_1.fileExists)(rootDir)))
|
|
58
|
+
return [];
|
|
59
|
+
const out = [];
|
|
60
|
+
const walk = async (current) => {
|
|
61
|
+
const entries = await promises_1.default.readdir(current, { withFileTypes: true });
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
const full = node_path_1.default.join(current, entry.name);
|
|
64
|
+
if (entry.isDirectory()) {
|
|
65
|
+
await walk(full);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
out.push(full);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
await walk(rootDir);
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
async function loadPreviousManifest(root) {
|
|
76
|
+
const manifestPath = node_path_1.default.join(root, "scaffold-manifest.json");
|
|
77
|
+
if (!(await (0, utils_1.fileExists)(manifestPath)))
|
|
78
|
+
return null;
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(await promises_1.default.readFile(manifestPath, "utf8"));
|
|
81
|
+
if (!Array.isArray(parsed.assets))
|
|
82
|
+
return null;
|
|
83
|
+
return {
|
|
84
|
+
schema_version: "1.0",
|
|
85
|
+
generated_at: parsed.generated_at ?? "",
|
|
86
|
+
prodo_version: parsed.prodo_version ?? "0.0.0-dev",
|
|
87
|
+
copied_asset_count: Number(parsed.copied_asset_count ?? 0),
|
|
88
|
+
copied_assets: Array.isArray(parsed.copied_assets) ? parsed.copied_assets : [],
|
|
89
|
+
asset_count: Number(parsed.asset_count ?? 0),
|
|
90
|
+
parity_summary: {
|
|
91
|
+
match_count: Number(parsed.parity_summary?.match_count ?? 0),
|
|
92
|
+
drift_count: Number(parsed.parity_summary?.drift_count ?? 0),
|
|
93
|
+
missing_count: Number(parsed.parity_summary?.missing_count ?? 0),
|
|
94
|
+
protected_count: Number(parsed.parity_summary?.protected_count ?? 0),
|
|
95
|
+
updated_count: Number(parsed.parity_summary?.updated_count ?? 0),
|
|
96
|
+
unmanaged_count: Number(parsed.parity_summary?.unmanaged_count ?? 0)
|
|
97
|
+
},
|
|
98
|
+
assets: parsed.assets
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function copyDirIfMissing(sourceDir, targetDir, copiedAssets) {
|
|
106
|
+
if (!(await (0, utils_1.fileExists)(sourceDir)))
|
|
107
|
+
return;
|
|
108
|
+
await (0, utils_1.ensureDir)(targetDir);
|
|
109
|
+
const entries = await promises_1.default.readdir(sourceDir, { withFileTypes: true });
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
const src = node_path_1.default.join(sourceDir, entry.name);
|
|
112
|
+
const dst = node_path_1.default.join(targetDir, entry.name);
|
|
113
|
+
if (entry.isDirectory()) {
|
|
114
|
+
await copyDirIfMissing(src, dst, copiedAssets);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (await (0, utils_1.fileExists)(dst))
|
|
118
|
+
continue;
|
|
119
|
+
await promises_1.default.copyFile(src, dst);
|
|
120
|
+
copiedAssets.push({
|
|
121
|
+
source: src,
|
|
122
|
+
target: dst,
|
|
123
|
+
sha256: await fileSha256(dst)
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function refreshLegacyCommandTemplates(sourceDir, targetDir) {
|
|
128
|
+
if (!(await (0, utils_1.fileExists)(sourceDir)) || !(await (0, utils_1.fileExists)(targetDir)))
|
|
129
|
+
return;
|
|
130
|
+
const entries = await promises_1.default.readdir(sourceDir, { withFileTypes: true });
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
if (!entry.isFile())
|
|
133
|
+
continue;
|
|
134
|
+
if (!entry.name.startsWith("prodo-") || !entry.name.endsWith(".md"))
|
|
135
|
+
continue;
|
|
136
|
+
const src = node_path_1.default.join(sourceDir, entry.name);
|
|
137
|
+
const dst = node_path_1.default.join(targetDir, entry.name);
|
|
138
|
+
if (!(await (0, utils_1.fileExists)(dst)))
|
|
139
|
+
continue;
|
|
140
|
+
const existing = await promises_1.default.readFile(dst, "utf8");
|
|
141
|
+
const isLegacyRunMode = /run:\s*\n\s*action:\s*[^\n]+/m.test(existing) || /mode:\s*internal-runtime/m.test(existing);
|
|
142
|
+
if (!isLegacyRunMode)
|
|
143
|
+
continue;
|
|
144
|
+
await promises_1.default.copyFile(src, dst);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function buildAssetManifest(pairs, previous, backup) {
|
|
148
|
+
const previousByTarget = new Map();
|
|
149
|
+
for (const item of previous?.assets ?? []) {
|
|
150
|
+
previousByTarget.set(node_path_1.default.resolve(item.target), item);
|
|
151
|
+
}
|
|
152
|
+
const items = [];
|
|
153
|
+
for (const pair of pairs) {
|
|
154
|
+
const sourceFiles = await listFilesRecursive(pair.sourceDir);
|
|
155
|
+
for (const sourceFile of sourceFiles) {
|
|
156
|
+
const relative = node_path_1.default.relative(pair.sourceDir, sourceFile);
|
|
157
|
+
const targetFile = node_path_1.default.join(pair.targetDir, relative);
|
|
158
|
+
const resolvedTarget = node_path_1.default.resolve(targetFile);
|
|
159
|
+
const sourceHash = await fileSha256(sourceFile);
|
|
160
|
+
const targetExists = await (0, utils_1.fileExists)(targetFile);
|
|
161
|
+
if (!targetExists) {
|
|
162
|
+
await (0, utils_1.ensureDir)(node_path_1.default.dirname(targetFile));
|
|
163
|
+
backup.set(resolvedTarget, null);
|
|
164
|
+
await promises_1.default.copyFile(sourceFile, targetFile);
|
|
165
|
+
items.push({
|
|
166
|
+
source: sourceFile,
|
|
167
|
+
target: targetFile,
|
|
168
|
+
source_sha256: sourceHash,
|
|
169
|
+
target_sha256: await fileSha256(targetFile),
|
|
170
|
+
status: "missing"
|
|
171
|
+
});
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const currentTargetHash = await fileSha256(targetFile);
|
|
175
|
+
const prev = previousByTarget.get(resolvedTarget);
|
|
176
|
+
const prevTargetHash = prev?.target_sha256 ?? null;
|
|
177
|
+
const prevSourceHash = prev?.source_sha256 ?? null;
|
|
178
|
+
if (currentTargetHash === sourceHash) {
|
|
179
|
+
items.push({
|
|
180
|
+
source: sourceFile,
|
|
181
|
+
target: targetFile,
|
|
182
|
+
source_sha256: sourceHash,
|
|
183
|
+
target_sha256: currentTargetHash,
|
|
184
|
+
status: "match"
|
|
185
|
+
});
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (prev && prevTargetHash && prevSourceHash && currentTargetHash === prevTargetHash && prevSourceHash !== sourceHash) {
|
|
189
|
+
if (!backup.has(resolvedTarget)) {
|
|
190
|
+
backup.set(resolvedTarget, await promises_1.default.readFile(targetFile));
|
|
191
|
+
}
|
|
192
|
+
await promises_1.default.copyFile(sourceFile, targetFile);
|
|
193
|
+
items.push({
|
|
194
|
+
source: sourceFile,
|
|
195
|
+
target: targetFile,
|
|
196
|
+
source_sha256: sourceHash,
|
|
197
|
+
target_sha256: await fileSha256(targetFile),
|
|
198
|
+
status: "updated"
|
|
199
|
+
});
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (prev) {
|
|
203
|
+
items.push({
|
|
204
|
+
source: sourceFile,
|
|
205
|
+
target: targetFile,
|
|
206
|
+
source_sha256: sourceHash,
|
|
207
|
+
target_sha256: currentTargetHash,
|
|
208
|
+
status: "protected"
|
|
209
|
+
});
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
items.push({
|
|
213
|
+
source: sourceFile,
|
|
214
|
+
target: targetFile,
|
|
215
|
+
source_sha256: sourceHash,
|
|
216
|
+
target_sha256: currentTargetHash,
|
|
217
|
+
status: "unmanaged"
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return items;
|
|
222
|
+
}
|
|
223
|
+
async function rollbackFiles(backup) {
|
|
224
|
+
for (const [target, content] of backup.entries()) {
|
|
225
|
+
if (content === null) {
|
|
226
|
+
if (await (0, utils_1.fileExists)(target))
|
|
227
|
+
await promises_1.default.rm(target, { force: true });
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
await (0, utils_1.ensureDir)(node_path_1.default.dirname(target));
|
|
231
|
+
await promises_1.default.writeFile(target, content);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function summarizeParity(items) {
|
|
235
|
+
const byStatus = (status) => items.filter((item) => item.status === status).length;
|
|
236
|
+
return {
|
|
237
|
+
match_count: byStatus("match"),
|
|
238
|
+
drift_count: byStatus("drift"),
|
|
239
|
+
missing_count: byStatus("missing"),
|
|
240
|
+
protected_count: byStatus("protected"),
|
|
241
|
+
updated_count: byStatus("updated"),
|
|
242
|
+
unmanaged_count: byStatus("unmanaged")
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async function runInit(cwd, options) {
|
|
246
|
+
const root = (0, paths_1.prodoPath)(cwd);
|
|
247
|
+
const artifactDefs = await (0, artifact_registry_1.listArtifactDefinitions)(cwd);
|
|
248
|
+
const artifactTypes = artifactDefs.map((item) => item.name);
|
|
249
|
+
const workflowCommands = (0, workflow_commands_1.buildWorkflowCommands)(artifactTypes);
|
|
250
|
+
const prodoVersion = await readProdoVersion(cwd);
|
|
251
|
+
const localRepoTemplates = node_path_1.default.join(cwd, "templates");
|
|
252
|
+
const packagedTemplates = node_path_1.default.resolve(__dirname, "..", "..", "templates");
|
|
253
|
+
const projectScaffoldTemplates = (await (0, utils_1.fileExists)(localRepoTemplates)) ? localRepoTemplates : packagedTemplates;
|
|
254
|
+
const copiedAssets = [];
|
|
255
|
+
const backup = new Map();
|
|
256
|
+
const previousManifest = await loadPreviousManifest(root);
|
|
257
|
+
await (0, utils_1.ensureDir)(node_path_1.default.join(root, "briefs"));
|
|
258
|
+
await (0, utils_1.ensureDir)(node_path_1.default.join(root, "schemas"));
|
|
259
|
+
await (0, utils_1.ensureDir)(node_path_1.default.join(root, "prompts"));
|
|
260
|
+
await (0, utils_1.ensureDir)(node_path_1.default.join(root, "commands"));
|
|
261
|
+
await (0, utils_1.ensureDir)(node_path_1.default.join(root, "presets"));
|
|
262
|
+
await (0, utils_1.ensureDir)(node_path_1.default.join(root, "templates"));
|
|
263
|
+
await (0, utils_1.ensureDir)(node_path_1.default.join(root, "templates", "overrides"));
|
|
264
|
+
await (0, utils_1.ensureDir)(node_path_1.default.join(root, "state"));
|
|
265
|
+
await (0, utils_1.ensureDir)(node_path_1.default.join(root, "state", "context"));
|
|
266
|
+
for (const def of artifactDefs) {
|
|
267
|
+
await (0, utils_1.ensureDir)((0, paths_1.outputDirPath)(cwd, def.name, def.output_dir));
|
|
268
|
+
}
|
|
269
|
+
await (0, utils_1.ensureDir)(node_path_1.default.join(cwd, "product-docs", "reports"));
|
|
270
|
+
await writeFileIfMissing((0, paths_1.outputIndexPath)(cwd), `${JSON.stringify({ active: {}, history: {}, updated_at: new Date(0).toISOString() }, null, 2)}\n`);
|
|
271
|
+
await writeFileIfMissing((0, paths_1.briefPath)(cwd), templates_1.START_BRIEF_TEMPLATE);
|
|
272
|
+
await writeFileIfMissing(node_path_1.default.join(root, "briefs", "normalized-brief.json"), `${JSON.stringify(templates_1.NORMALIZED_BRIEF_TEMPLATE, null, 2)}\n`);
|
|
273
|
+
await writeFileIfMissing(node_path_1.default.join(root, "hooks.yml"), templates_1.HOOKS_TEMPLATE);
|
|
274
|
+
await writeFileIfMissing(node_path_1.default.join(root, "prompts", "normalize.md"), `${templates_1.NORMALIZE_PROMPT_TEMPLATE}\n`);
|
|
275
|
+
const scriptType = options?.script ?? (process.platform === "win32" ? "ps" : "sh");
|
|
276
|
+
await promises_1.default.writeFile(node_path_1.default.join(root, "init-options.json"), `${JSON.stringify({ ai: options?.ai ?? null, lang: options?.lang ?? "en", author: options?.author ?? null, preset: options?.preset ?? null, script: scriptType }, null, 2)}\n`, "utf8");
|
|
277
|
+
await copyDirIfMissing(node_path_1.default.join(projectScaffoldTemplates, "artifacts"), node_path_1.default.join(root, "templates"), copiedAssets);
|
|
278
|
+
for (const artifact of artifactDefs) {
|
|
279
|
+
const markdownTemplatePath = node_path_1.default.join(root, "templates", `${artifact.name}.md`);
|
|
280
|
+
const templateHeadings = (await (0, utils_1.fileExists)(markdownTemplatePath))
|
|
281
|
+
? (0, template_resolver_1.extractRequiredHeadingsFromTemplate)(await promises_1.default.readFile(markdownTemplatePath, "utf8"))
|
|
282
|
+
: [];
|
|
283
|
+
const schema = {
|
|
284
|
+
...(0, templates_1.schemaTemplate)(artifact.name),
|
|
285
|
+
x_required_headings: templateHeadings.length > 0 ? templateHeadings : artifact.required_headings
|
|
286
|
+
};
|
|
287
|
+
await writeFileIfMissing(node_path_1.default.join(root, "schemas", `${artifact.name}.yaml`), js_yaml_1.default.dump(schema));
|
|
288
|
+
await writeFileIfMissing(node_path_1.default.join(root, "prompts", `${artifact.name}.md`), `${(0, templates_1.promptTemplate)(artifact.name, options?.lang ?? "en")}\n`);
|
|
289
|
+
await writeFileIfMissing(node_path_1.default.join(root, "templates", templateFileName(artifact.name)), `${(0, templates_1.artifactTemplateTemplate)(artifact.name, options?.lang ?? "en")}\n`);
|
|
290
|
+
}
|
|
291
|
+
await copyDirIfMissing(node_path_1.default.join(projectScaffoldTemplates, "commands"), node_path_1.default.join(root, "commands"), copiedAssets);
|
|
292
|
+
await refreshLegacyCommandTemplates(node_path_1.default.join(projectScaffoldTemplates, "commands"), node_path_1.default.join(root, "commands"));
|
|
293
|
+
for (const command of workflowCommands) {
|
|
294
|
+
await writeFileIfMissing(node_path_1.default.join(root, "commands", `${command.name}.md`), `${(0, templates_1.commandTemplate)(command)}\n`);
|
|
295
|
+
}
|
|
296
|
+
await (0, preset_loader_1.applyConfiguredPresets)(cwd, root, prodoVersion, options?.preset);
|
|
297
|
+
const pairs = [
|
|
298
|
+
{
|
|
299
|
+
sourceDir: node_path_1.default.join(projectScaffoldTemplates, "commands"),
|
|
300
|
+
targetDir: node_path_1.default.join(root, "commands")
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
sourceDir: node_path_1.default.join(projectScaffoldTemplates, "artifacts"),
|
|
304
|
+
targetDir: node_path_1.default.join(root, "templates")
|
|
305
|
+
}
|
|
306
|
+
];
|
|
307
|
+
let parity = [];
|
|
308
|
+
try {
|
|
309
|
+
parity = await buildAssetManifest(pairs, previousManifest, backup);
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
await rollbackFiles(backup);
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
const installedAgentFiles = options?.ai ? await (0, agent_command_installer_1.installAgentCommands)(cwd, options.ai) : [];
|
|
316
|
+
const manifest = {
|
|
317
|
+
schema_version: "1.0",
|
|
318
|
+
generated_at: new Date().toISOString(),
|
|
319
|
+
prodo_version: prodoVersion,
|
|
320
|
+
copied_asset_count: copiedAssets.length,
|
|
321
|
+
copied_assets: copiedAssets,
|
|
322
|
+
asset_count: parity.length,
|
|
323
|
+
parity_summary: summarizeParity(parity),
|
|
324
|
+
assets: parity
|
|
325
|
+
};
|
|
326
|
+
await promises_1.default.writeFile(node_path_1.default.join(root, "scaffold-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
327
|
+
await (0, registry_1.syncRegistry)(cwd);
|
|
328
|
+
const settingsPath = await (0, settings_1.writeSettings)(cwd, {
|
|
329
|
+
lang: (options?.lang ?? "en").trim() || "en",
|
|
330
|
+
ai: options?.ai,
|
|
331
|
+
author: (options?.author ?? "").trim() || undefined,
|
|
332
|
+
provider: options?.provider
|
|
333
|
+
});
|
|
334
|
+
return { installedAgentFiles, settingsPath };
|
|
335
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
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.runInteractiveNormalize = runInteractiveNormalize;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const normalized_brief_1 = require("../core/normalized-brief");
|
|
10
|
+
const normalize_1 = require("../core/normalize");
|
|
11
|
+
const paths_1 = require("../core/paths");
|
|
12
|
+
const utils_1 = require("../core/utils");
|
|
13
|
+
const errors_1 = require("../core/errors");
|
|
14
|
+
const CONFIDENCE_FIELDS = [
|
|
15
|
+
"product_name",
|
|
16
|
+
"problem",
|
|
17
|
+
"audience",
|
|
18
|
+
"goals",
|
|
19
|
+
"core_features"
|
|
20
|
+
];
|
|
21
|
+
const FIELD_QUESTIONS = {
|
|
22
|
+
product_name: (val) => `The product name was detected as "${val ?? "(empty)"}". Can you confirm or provide the exact product name?`,
|
|
23
|
+
problem: (val) => val
|
|
24
|
+
? `The problem statement seems unclear: "${String(val).slice(0, 80)}...". Can you describe the core problem more precisely?`
|
|
25
|
+
: "What is the core problem this product solves?",
|
|
26
|
+
audience: (val) => Array.isArray(val) && val.length > 0
|
|
27
|
+
? `The target audience was detected as: ${val.join(", ")}. Is this correct? Add any missing groups.`
|
|
28
|
+
: "Who is the target audience for this product?",
|
|
29
|
+
goals: (val) => Array.isArray(val) && val.length > 0
|
|
30
|
+
? `The following goals were identified: ${val.join("; ")}. Are these correct? Add any missing goals.`
|
|
31
|
+
: "What are the primary goals of this product?",
|
|
32
|
+
core_features: (val) => Array.isArray(val) && val.length > 0
|
|
33
|
+
? `These core features were detected: ${val.join("; ")}. Are these correct? Add any missing features.`
|
|
34
|
+
: "What are the core features of this product?"
|
|
35
|
+
};
|
|
36
|
+
async function loadHistory(cwd) {
|
|
37
|
+
const file = (0, paths_1.normHistoryPath)(cwd);
|
|
38
|
+
if (!(await (0, utils_1.fileExists)(file))) {
|
|
39
|
+
return { version: "1.0", sessions: [] };
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return await (0, utils_1.readJsonFile)(file);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { version: "1.0", sessions: [] };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function saveHistory(cwd, history) {
|
|
49
|
+
const file = (0, paths_1.normHistoryPath)(cwd);
|
|
50
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(file), { recursive: true });
|
|
51
|
+
await promises_1.default.writeFile(file, `${JSON.stringify(history, null, 2)}\n`, "utf8");
|
|
52
|
+
}
|
|
53
|
+
function buildQuestions(brief, confidenceResult) {
|
|
54
|
+
return confidenceResult.lowFields.map(({ field, confidence }) => {
|
|
55
|
+
const current = brief[field];
|
|
56
|
+
const questionFn = FIELD_QUESTIONS[field];
|
|
57
|
+
const question = questionFn ? questionFn(current) : `Please clarify the "${field}" field.`;
|
|
58
|
+
return { field, question, confidence };
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async function runInteractiveNormalize(options) {
|
|
62
|
+
const { cwd, maxIterations = 3, log = console.log } = options;
|
|
63
|
+
const isTTY = process.stdout.isTTY === true;
|
|
64
|
+
if (!isTTY) {
|
|
65
|
+
log("Non-interactive terminal detected. Running standard normalize instead.");
|
|
66
|
+
return (0, normalize_1.runNormalize)({ cwd, brief: options.brief, out: options.out });
|
|
67
|
+
}
|
|
68
|
+
const clack = await loadClack();
|
|
69
|
+
if (!clack) {
|
|
70
|
+
log("Interactive prompts not available. Running standard normalize.");
|
|
71
|
+
return (0, normalize_1.runNormalize)({ cwd, brief: options.brief, out: options.out });
|
|
72
|
+
}
|
|
73
|
+
const history = await loadHistory(cwd);
|
|
74
|
+
let additionalContext = {};
|
|
75
|
+
let iteration = 0;
|
|
76
|
+
clack.intro("Prodo Interactive Normalize");
|
|
77
|
+
while (iteration < maxIterations) {
|
|
78
|
+
iteration += 1;
|
|
79
|
+
log(`\nNormalize iteration ${iteration}/${maxIterations}...`);
|
|
80
|
+
const outPath = await runNormalizeRelaxed({
|
|
81
|
+
cwd,
|
|
82
|
+
brief: options.brief,
|
|
83
|
+
out: options.out,
|
|
84
|
+
additionalContext
|
|
85
|
+
});
|
|
86
|
+
const normalizedRaw = JSON.parse(await promises_1.default.readFile(outPath, "utf8"));
|
|
87
|
+
const brief = (0, normalized_brief_1.parseNormalizedBriefOrThrow)(normalizedRaw);
|
|
88
|
+
const result = (0, normalized_brief_1.checkConfidence)(brief, [...CONFIDENCE_FIELDS], 0.7);
|
|
89
|
+
if (result.pass) {
|
|
90
|
+
log("All fields have sufficient confidence.");
|
|
91
|
+
clack.outro("Normalization complete!");
|
|
92
|
+
await saveHistory(cwd, history);
|
|
93
|
+
return outPath;
|
|
94
|
+
}
|
|
95
|
+
const questions = buildQuestions(brief, result);
|
|
96
|
+
log(`\n${questions.length} field(s) need clarification:\n`);
|
|
97
|
+
const session = {
|
|
98
|
+
timestamp: new Date().toISOString(),
|
|
99
|
+
iteration,
|
|
100
|
+
questions,
|
|
101
|
+
answers: {}
|
|
102
|
+
};
|
|
103
|
+
for (const q of questions) {
|
|
104
|
+
const answer = await clack.text({
|
|
105
|
+
message: `[${(q.confidence * 100).toFixed(0)}% confidence] ${q.question}`,
|
|
106
|
+
placeholder: "Type your answer or press Enter to skip..."
|
|
107
|
+
});
|
|
108
|
+
if (clack.isCancel(answer)) {
|
|
109
|
+
clack.cancel("Normalize cancelled.");
|
|
110
|
+
await saveHistory(cwd, history);
|
|
111
|
+
return outPath;
|
|
112
|
+
}
|
|
113
|
+
const answerStr = typeof answer === "string" ? answer.trim() : "";
|
|
114
|
+
if (answerStr.length > 0) {
|
|
115
|
+
additionalContext[q.field] = answerStr;
|
|
116
|
+
session.answers[q.field] = answerStr;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
history.sessions.push(session);
|
|
120
|
+
if (Object.keys(session.answers).length === 0) {
|
|
121
|
+
log("No clarifications provided. Keeping current normalization.");
|
|
122
|
+
clack.outro("Normalization complete (as-is).");
|
|
123
|
+
await saveHistory(cwd, history);
|
|
124
|
+
return outPath;
|
|
125
|
+
}
|
|
126
|
+
const shouldContinue = await clack.confirm({
|
|
127
|
+
message: "Continue refining? (No = accept current result)"
|
|
128
|
+
});
|
|
129
|
+
if (clack.isCancel(shouldContinue) || shouldContinue === false) {
|
|
130
|
+
clack.outro("Normalization accepted.");
|
|
131
|
+
await saveHistory(cwd, history);
|
|
132
|
+
return outPath;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
log(`Maximum iterations (${maxIterations}) reached.`);
|
|
136
|
+
clack.outro("Normalization complete.");
|
|
137
|
+
await saveHistory(cwd, history);
|
|
138
|
+
const finalPath = options.out
|
|
139
|
+
? node_path_1.default.resolve(cwd, options.out)
|
|
140
|
+
: (0, paths_1.normalizedBriefPath)(cwd);
|
|
141
|
+
return finalPath;
|
|
142
|
+
}
|
|
143
|
+
async function runNormalizeRelaxed(options) {
|
|
144
|
+
try {
|
|
145
|
+
return await (0, normalize_1.runNormalize)(options);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
if (error instanceof errors_1.UserError &&
|
|
149
|
+
error.message.includes("Normalization confidence too low")) {
|
|
150
|
+
const outPath = options.out
|
|
151
|
+
? node_path_1.default.resolve(options.cwd, options.out)
|
|
152
|
+
: (0, paths_1.normalizedBriefPath)(options.cwd);
|
|
153
|
+
if (await (0, utils_1.fileExists)(outPath))
|
|
154
|
+
return outPath;
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
160
|
+
async function loadClack() {
|
|
161
|
+
try {
|
|
162
|
+
return (await dynamicImport("@clack/prompts"));
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|