@shahmarasy/prodo 0.1.5 → 0.1.7
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.d.ts +1 -1
- package/dist/cli/agent-command-installer.js +125 -19
- 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 +3 -2
- 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/agent-command-installer.ts +115 -1
- 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 +4 -3
- 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
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ValidationIssue } from "../core/types";
|
|
2
|
+
export type SkillInputType = "string" | "path" | "boolean" | "json" | "number";
|
|
3
|
+
export type SkillInput = {
|
|
4
|
+
name: string;
|
|
5
|
+
type: SkillInputType;
|
|
6
|
+
required: boolean;
|
|
7
|
+
description?: string;
|
|
8
|
+
default?: unknown;
|
|
9
|
+
};
|
|
10
|
+
export type SkillOutput = {
|
|
11
|
+
name: string;
|
|
12
|
+
type: SkillInputType;
|
|
13
|
+
description?: string;
|
|
14
|
+
};
|
|
15
|
+
export type SkillManifest = {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
description: string;
|
|
19
|
+
category: "core" | "artifact" | "validation" | "custom";
|
|
20
|
+
depends_on: string[];
|
|
21
|
+
inputs: SkillInput[];
|
|
22
|
+
outputs: SkillOutput[];
|
|
23
|
+
tags?: string[];
|
|
24
|
+
};
|
|
25
|
+
export type PipelineState = {
|
|
26
|
+
cwd: string;
|
|
27
|
+
normalizedBriefPath?: string;
|
|
28
|
+
generatedArtifacts: Map<string, string>;
|
|
29
|
+
validationResult?: {
|
|
30
|
+
pass: boolean;
|
|
31
|
+
reportPath: string;
|
|
32
|
+
issues: ValidationIssue[];
|
|
33
|
+
};
|
|
34
|
+
custom: Record<string, unknown>;
|
|
35
|
+
startedAt: string;
|
|
36
|
+
completedSkills: string[];
|
|
37
|
+
};
|
|
38
|
+
export type ProgressCallback = (step: number, total: number, message: string) => void;
|
|
39
|
+
export type SkillContext = {
|
|
40
|
+
state: PipelineState;
|
|
41
|
+
progress: ProgressCallback;
|
|
42
|
+
log: (message: string) => void;
|
|
43
|
+
agent?: string;
|
|
44
|
+
};
|
|
45
|
+
export type SkillExecuteFn = (context: SkillContext, inputs: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
46
|
+
export type Skill = {
|
|
47
|
+
manifest: SkillManifest;
|
|
48
|
+
execute: SkillExecuteFn;
|
|
49
|
+
};
|
|
50
|
+
export type SkillError = {
|
|
51
|
+
skillName: string;
|
|
52
|
+
phase: "input_validation" | "execution" | "output_validation";
|
|
53
|
+
message: string;
|
|
54
|
+
inputContext?: Record<string, unknown>;
|
|
55
|
+
stack?: string;
|
|
56
|
+
recoveryHints: string[];
|
|
57
|
+
};
|
|
58
|
+
export type ExecutionTier = {
|
|
59
|
+
tier: number;
|
|
60
|
+
skills: string[];
|
|
61
|
+
};
|
|
62
|
+
export type PipelineOptions = {
|
|
63
|
+
log?: (message: string) => void;
|
|
64
|
+
progress?: ProgressCallback;
|
|
65
|
+
agent?: string;
|
|
66
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SkillError, SkillManifest } from "./types";
|
|
2
|
+
export declare function validateInputs(manifest: SkillManifest, inputs: Record<string, unknown>): SkillError | null;
|
|
3
|
+
export declare function validateInputPaths(manifest: SkillManifest, inputs: Record<string, unknown>): Promise<SkillError | null>;
|
|
4
|
+
export declare function validateOutputs(manifest: SkillManifest, outputs: Record<string, unknown>): SkillError | null;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateInputs = validateInputs;
|
|
4
|
+
exports.validateInputPaths = validateInputPaths;
|
|
5
|
+
exports.validateOutputs = validateOutputs;
|
|
6
|
+
const utils_1 = require("../core/utils");
|
|
7
|
+
function makeError(skillName, phase, message, hints = []) {
|
|
8
|
+
return {
|
|
9
|
+
skillName,
|
|
10
|
+
phase,
|
|
11
|
+
message,
|
|
12
|
+
recoveryHints: hints
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function checkType(value, expectedType, fieldName) {
|
|
16
|
+
switch (expectedType) {
|
|
17
|
+
case "string":
|
|
18
|
+
if (typeof value !== "string")
|
|
19
|
+
return `"${fieldName}" must be a string, got ${typeof value}`;
|
|
20
|
+
return null;
|
|
21
|
+
case "path":
|
|
22
|
+
if (typeof value !== "string")
|
|
23
|
+
return `"${fieldName}" must be a path (string), got ${typeof value}`;
|
|
24
|
+
return null;
|
|
25
|
+
case "boolean":
|
|
26
|
+
if (typeof value !== "boolean")
|
|
27
|
+
return `"${fieldName}" must be a boolean, got ${typeof value}`;
|
|
28
|
+
return null;
|
|
29
|
+
case "number":
|
|
30
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
31
|
+
return `"${fieldName}" must be a finite number, got ${typeof value}`;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
case "json":
|
|
35
|
+
if (typeof value === "string") {
|
|
36
|
+
try {
|
|
37
|
+
JSON.parse(value);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return `"${fieldName}" must be valid JSON string`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
default:
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function validateInputs(manifest, inputs) {
|
|
49
|
+
for (const input of manifest.inputs) {
|
|
50
|
+
const value = inputs[input.name];
|
|
51
|
+
if (input.required && (value === undefined || value === null)) {
|
|
52
|
+
if (input.default !== undefined)
|
|
53
|
+
continue;
|
|
54
|
+
return makeError(manifest.name, "input_validation", `Required input "${input.name}" is missing`, [`Provide "${input.name}" (${input.type}): ${input.description ?? ""}`]);
|
|
55
|
+
}
|
|
56
|
+
if (value === undefined || value === null)
|
|
57
|
+
continue;
|
|
58
|
+
const typeError = checkType(value, input.type, input.name);
|
|
59
|
+
if (typeError) {
|
|
60
|
+
return makeError(manifest.name, "input_validation", typeError, [`Expected type: ${input.type}`]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
async function validateInputPaths(manifest, inputs) {
|
|
66
|
+
for (const input of manifest.inputs) {
|
|
67
|
+
if (input.type !== "path")
|
|
68
|
+
continue;
|
|
69
|
+
const value = inputs[input.name];
|
|
70
|
+
if (typeof value !== "string")
|
|
71
|
+
continue;
|
|
72
|
+
if (input.required && !(await (0, utils_1.fileExists)(value))) {
|
|
73
|
+
return makeError(manifest.name, "input_validation", `Path "${input.name}" does not exist: ${value}`, [`Ensure the file or directory exists: ${value}`]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function validateOutputs(manifest, outputs) {
|
|
79
|
+
for (const output of manifest.outputs) {
|
|
80
|
+
const value = outputs[output.name];
|
|
81
|
+
if (value === undefined || value === null) {
|
|
82
|
+
return makeError(manifest.name, "output_validation", `Expected output "${output.name}" was not produced`, [`Skill "${manifest.name}" should return "${output.name}" (${output.type})`]);
|
|
83
|
+
}
|
|
84
|
+
const typeError = checkType(value, output.type, output.name);
|
|
85
|
+
if (typeError) {
|
|
86
|
+
return makeError(manifest.name, "output_validation", typeError, [`Skill "${manifest.name}" returned wrong type for "${output.name}"`]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fixSkill = void 0;
|
|
4
|
+
const fix_1 = require("../core/fix");
|
|
5
|
+
exports.fixSkill = {
|
|
6
|
+
manifest: {
|
|
7
|
+
name: "fix",
|
|
8
|
+
version: "2.0.0",
|
|
9
|
+
description: "Auto-regenerate failing artifacts with backup, proposal, and validation",
|
|
10
|
+
category: "validation",
|
|
11
|
+
depends_on: [],
|
|
12
|
+
inputs: [
|
|
13
|
+
{ name: "cwd", type: "path", required: true, description: "Project working directory" }
|
|
14
|
+
],
|
|
15
|
+
outputs: [
|
|
16
|
+
{ name: "validationResult", type: "json", description: "Final validation result after fix" }
|
|
17
|
+
],
|
|
18
|
+
tags: ["fix", "repair", "validation"]
|
|
19
|
+
},
|
|
20
|
+
async execute(context, inputs) {
|
|
21
|
+
const cwd = inputs.cwd ?? context.state.cwd;
|
|
22
|
+
context.progress(1, 3, "Running fix pipeline...");
|
|
23
|
+
const result = await (0, fix_1.runFix)({
|
|
24
|
+
cwd,
|
|
25
|
+
agent: context.agent,
|
|
26
|
+
log: context.log
|
|
27
|
+
});
|
|
28
|
+
context.progress(3, 3, result.finalPass ? "Fix complete." : "Fix applied, issues remain.");
|
|
29
|
+
context.state.validationResult = result.finalPass
|
|
30
|
+
? { pass: true, reportPath: result.reportPath, issues: [] }
|
|
31
|
+
: undefined;
|
|
32
|
+
return {
|
|
33
|
+
validationResult: {
|
|
34
|
+
pass: result.finalPass,
|
|
35
|
+
reportPath: result.reportPath,
|
|
36
|
+
applied: result.applied,
|
|
37
|
+
targetCount: result.proposal.targets.length
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createArtifactSkill = createArtifactSkill;
|
|
4
|
+
const artifacts_1 = require("../core/artifacts");
|
|
5
|
+
const constants_1 = require("../core/constants");
|
|
6
|
+
function createArtifactSkill(artifactType, upstream) {
|
|
7
|
+
const resolvedUpstream = upstream ?? (0, constants_1.defaultUpstreamByArtifact)(artifactType);
|
|
8
|
+
const depends_on = ["normalize", ...resolvedUpstream];
|
|
9
|
+
const manifest = {
|
|
10
|
+
name: artifactType,
|
|
11
|
+
version: "2.0.0",
|
|
12
|
+
description: `Generate ${artifactType} artifact from normalized brief`,
|
|
13
|
+
category: "artifact",
|
|
14
|
+
depends_on,
|
|
15
|
+
inputs: [
|
|
16
|
+
{ name: "cwd", type: "path", required: true, description: "Project working directory" },
|
|
17
|
+
{ name: "normalizedBriefPath", type: "path", required: false, description: "Path to normalized brief" }
|
|
18
|
+
],
|
|
19
|
+
outputs: [
|
|
20
|
+
{ name: "artifactPath", type: "path", description: `Path to generated ${artifactType}` }
|
|
21
|
+
],
|
|
22
|
+
tags: ["artifact", artifactType]
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
manifest,
|
|
26
|
+
async execute(context, inputs) {
|
|
27
|
+
const cwd = inputs.cwd ?? context.state.cwd;
|
|
28
|
+
const normalizedBriefOverride = inputs.normalizedBriefPath ?? context.state.normalizedBriefPath;
|
|
29
|
+
context.progress(1, 2, `Generating ${artifactType}...`);
|
|
30
|
+
const filePath = await (0, artifacts_1.generateArtifact)({
|
|
31
|
+
artifactType,
|
|
32
|
+
cwd,
|
|
33
|
+
normalizedBriefOverride,
|
|
34
|
+
agent: context.agent
|
|
35
|
+
});
|
|
36
|
+
context.progress(2, 2, `${artifactType} generated.`);
|
|
37
|
+
context.log(`${artifactType.toUpperCase()} generated: ${filePath}`);
|
|
38
|
+
context.state.generatedArtifacts.set(artifactType, filePath);
|
|
39
|
+
return { artifactPath: filePath };
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeSkill = void 0;
|
|
4
|
+
const normalize_1 = require("../core/normalize");
|
|
5
|
+
exports.normalizeSkill = {
|
|
6
|
+
manifest: {
|
|
7
|
+
name: "normalize",
|
|
8
|
+
version: "2.0.0",
|
|
9
|
+
description: "Normalize a product brief into structured JSON with contracts and confidence scores",
|
|
10
|
+
category: "core",
|
|
11
|
+
depends_on: [],
|
|
12
|
+
inputs: [
|
|
13
|
+
{ name: "cwd", type: "path", required: true, description: "Project working directory" }
|
|
14
|
+
],
|
|
15
|
+
outputs: [
|
|
16
|
+
{ name: "normalizedBriefPath", type: "path", description: "Path to normalized-brief.json" }
|
|
17
|
+
],
|
|
18
|
+
tags: ["brief", "normalization"]
|
|
19
|
+
},
|
|
20
|
+
async execute(context, inputs) {
|
|
21
|
+
const cwd = inputs.cwd ?? context.state.cwd;
|
|
22
|
+
context.progress(1, 2, "Normalizing brief...");
|
|
23
|
+
const outPath = await (0, normalize_1.runNormalize)({ cwd });
|
|
24
|
+
context.progress(2, 2, "Brief normalized.");
|
|
25
|
+
context.log(`Normalized brief written to: ${outPath}`);
|
|
26
|
+
context.state.normalizedBriefPath = outPath;
|
|
27
|
+
return { normalizedBriefPath: outPath };
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCoreSkills = registerCoreSkills;
|
|
4
|
+
const artifact_registry_1 = require("../core/artifact-registry");
|
|
5
|
+
const generate_artifact_1 = require("./generate-artifact");
|
|
6
|
+
const fix_1 = require("./fix");
|
|
7
|
+
const normalize_1 = require("./normalize");
|
|
8
|
+
const validate_1 = require("./validate");
|
|
9
|
+
async function registerCoreSkills(registry, cwd) {
|
|
10
|
+
registry.register(normalize_1.normalizeSkill);
|
|
11
|
+
const definitions = await (0, artifact_registry_1.listArtifactDefinitions)(cwd);
|
|
12
|
+
const artifactNames = [];
|
|
13
|
+
for (const def of definitions) {
|
|
14
|
+
const skill = (0, generate_artifact_1.createArtifactSkill)(def.name, def.upstream);
|
|
15
|
+
registry.register(skill);
|
|
16
|
+
artifactNames.push(def.name);
|
|
17
|
+
}
|
|
18
|
+
const validateSkill = (0, validate_1.createValidateSkill)(artifactNames);
|
|
19
|
+
registry.register(validateSkill);
|
|
20
|
+
registry.register(fix_1.fixSkill);
|
|
21
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createValidateSkill = createValidateSkill;
|
|
4
|
+
const validate_1 = require("../core/validate");
|
|
5
|
+
function createValidateSkill(artifactTypes) {
|
|
6
|
+
return {
|
|
7
|
+
manifest: {
|
|
8
|
+
name: "validate",
|
|
9
|
+
version: "2.0.0",
|
|
10
|
+
description: "Run 7-gate cross-artifact validation and generate report",
|
|
11
|
+
category: "validation",
|
|
12
|
+
depends_on: [...artifactTypes],
|
|
13
|
+
inputs: [
|
|
14
|
+
{ name: "cwd", type: "path", required: true, description: "Project working directory" }
|
|
15
|
+
],
|
|
16
|
+
outputs: [
|
|
17
|
+
{ name: "validationResult", type: "json", description: "Validation result with pass/fail and issues" }
|
|
18
|
+
],
|
|
19
|
+
tags: ["validation", "consistency"]
|
|
20
|
+
},
|
|
21
|
+
async execute(context, inputs) {
|
|
22
|
+
const cwd = inputs.cwd ?? context.state.cwd;
|
|
23
|
+
context.progress(1, 2, "Validating artifacts...");
|
|
24
|
+
const result = await (0, validate_1.runValidate)(cwd, {});
|
|
25
|
+
context.progress(2, 2, `Validation ${result.pass ? "passed" : "failed"}.`);
|
|
26
|
+
context.log(`Validation ${result.pass ? "passed" : "failed"}: ${result.reportPath}`);
|
|
27
|
+
context.state.validationResult = result;
|
|
28
|
+
return {
|
|
29
|
+
validationResult: {
|
|
30
|
+
pass: result.pass,
|
|
31
|
+
reportPath: result.reportPath,
|
|
32
|
+
issueCount: result.issues.length
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shahmarasy/prodo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "CLI-first, prompt-powered product artifact kit",
|
|
5
5
|
"main": "dist/cli/index.js",
|
|
6
6
|
"types": "dist/cli/index.d.ts",
|
|
@@ -22,11 +22,9 @@
|
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
|
-
"build
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"test": "npm run build && node --test tests/*.test.cjs",
|
|
29
|
-
"verify:release": "npm run build && node scripts/verify-release-build.cjs"
|
|
25
|
+
"build": "node scripts/build.cjs",
|
|
26
|
+
"test": "node scripts/test.cjs",
|
|
27
|
+
"verify:release": "node scripts/build.cjs"
|
|
30
28
|
},
|
|
31
29
|
"keywords": [
|
|
32
30
|
"product-management",
|
|
@@ -122,7 +122,97 @@ async function loadCommandTemplateNames(commandTemplatesDir: string): Promise<st
|
|
|
122
122
|
.sort();
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
function generateClaudeMd(projectRoot: string, lang: string): string {
|
|
126
|
+
const projectName = path.basename(projectRoot) || "Product";
|
|
127
|
+
return `# Prodo — Product Artifact Workspace
|
|
128
|
+
|
|
129
|
+
This project uses **Prodo** to generate product documentation from a brief.
|
|
130
|
+
|
|
131
|
+
## Project Structure
|
|
132
|
+
|
|
133
|
+
- \`brief.md\` — Product brief (INPUT — read-only during generation)
|
|
134
|
+
- \`product-docs/\` — Generated artifacts (OUTPUT)
|
|
135
|
+
- \`.prodo/\` — Internal scaffold (templates, schemas, prompts, state)
|
|
136
|
+
|
|
137
|
+
## Workflow
|
|
138
|
+
|
|
139
|
+
Run commands in this sequence:
|
|
140
|
+
|
|
141
|
+
1. \`/prodo-normalize\` — Parse brief into structured JSON
|
|
142
|
+
2. \`/prodo-prd\` — Generate Product Requirements Document
|
|
143
|
+
3. \`/prodo-workflow\` — Generate workflow diagrams (Markdown + Mermaid)
|
|
144
|
+
4. \`/prodo-wireframe\` — Generate wireframes (Markdown + HTML)
|
|
145
|
+
5. \`/prodo-stories\` — Generate user stories with Gherkin scenarios
|
|
146
|
+
6. \`/prodo-techspec\` — Generate technical specification
|
|
147
|
+
7. \`/prodo-validate\` — Cross-artifact validation (7 gates)
|
|
148
|
+
|
|
149
|
+
## Rules
|
|
150
|
+
|
|
151
|
+
- **Never modify \`brief.md\` during command execution** — it is read-only input
|
|
152
|
+
- **All artifacts must include contract tags** like \`[G1]\`, \`[F2]\`, \`[C1]\` for traceability
|
|
153
|
+
- **Output language is \`${lang}\`** — do not mix languages in generated content
|
|
154
|
+
- **Follow templates strictly** — each artifact has a template in \`.prodo/templates/\`
|
|
155
|
+
- **Validate after generation** — run \`/prodo-validate\` to check consistency
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function generateArtifactRules(lang: string): string {
|
|
160
|
+
return `# Artifact Format Rules
|
|
161
|
+
|
|
162
|
+
## Contract Tags
|
|
163
|
+
Every artifact must reference brief requirements using tags:
|
|
164
|
+
- \`[G1]\`, \`[G2]\` — Goal references
|
|
165
|
+
- \`[F1]\`, \`[F2]\` — Feature references
|
|
166
|
+
- \`[C1]\`, \`[C2]\` — Constraint references
|
|
167
|
+
|
|
168
|
+
Tags must appear inline where the requirement is addressed.
|
|
169
|
+
|
|
170
|
+
## Frontmatter
|
|
171
|
+
Every generated artifact must include YAML frontmatter:
|
|
172
|
+
\`\`\`yaml
|
|
173
|
+
artifact_type: prd | workflow | wireframe | stories | techspec
|
|
174
|
+
version: <timestamp>
|
|
175
|
+
source_brief: <path to normalized-brief.json>
|
|
176
|
+
generated_at: <ISO timestamp>
|
|
177
|
+
status: draft
|
|
178
|
+
contract_coverage:
|
|
179
|
+
goals: [G1, G2]
|
|
180
|
+
core_features: [F1, F2]
|
|
181
|
+
constraints: [C1]
|
|
182
|
+
\`\`\`
|
|
183
|
+
|
|
184
|
+
## Paired Outputs
|
|
185
|
+
- **Workflow**: Produces \`.md\` (narrative) + \`.mmd\` (Mermaid diagram)
|
|
186
|
+
- **Wireframe**: Produces \`.md\` (description) + \`.html\` (low-fidelity prototype)
|
|
187
|
+
|
|
188
|
+
## Language
|
|
189
|
+
Output language: **${lang}**. Do not mix languages. Required heading names stay in English.
|
|
190
|
+
`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function generateBriefRules(): string {
|
|
194
|
+
return `# Brief Writing Rules
|
|
195
|
+
|
|
196
|
+
## Structure
|
|
197
|
+
The brief (\`brief.md\`) should contain these sections:
|
|
198
|
+
|
|
199
|
+
- **Product Name** — Clear, specific product name
|
|
200
|
+
- **Problem** — Core problem the product solves (be specific)
|
|
201
|
+
- **Audience** — Target users (list distinct personas)
|
|
202
|
+
- **Goals** — Measurable objectives (not vague aspirations)
|
|
203
|
+
- **Core Features** — One capability per bullet point
|
|
204
|
+
- **Constraints** — Technical, business, or regulatory limits
|
|
205
|
+
- **Assumptions** — Open questions or working assumptions
|
|
206
|
+
|
|
207
|
+
## Tips
|
|
208
|
+
- Be specific: "Reduce checkout abandonment by 30%" not "Improve UX"
|
|
209
|
+
- Use consistent terminology throughout
|
|
210
|
+
- Each feature should be distinct and independently describable
|
|
211
|
+
- Constraints should be concrete: "Node.js 20+", "GDPR compliant"
|
|
212
|
+
`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function installAgentCommands(projectRoot: string, ai: SupportedAi, lang?: string): Promise<string[]> {
|
|
126
216
|
const cfg = AGENT_CONFIG[ai];
|
|
127
217
|
const target = path.join(projectRoot, cfg.baseDir);
|
|
128
218
|
const commandTemplatesDir = path.join(projectRoot, ".prodo", "commands");
|
|
@@ -130,6 +220,30 @@ export async function installAgentCommands(projectRoot: string, ai: SupportedAi)
|
|
|
130
220
|
await ensureDir(target);
|
|
131
221
|
|
|
132
222
|
const written: string[] = [];
|
|
223
|
+
|
|
224
|
+
if (ai === "claude-cli") {
|
|
225
|
+
const claudeMdPath = path.join(projectRoot, "CLAUDE.md");
|
|
226
|
+
if (!(await fileExists(claudeMdPath))) {
|
|
227
|
+
await fs.writeFile(claudeMdPath, generateClaudeMd(projectRoot, lang ?? "en"), "utf8");
|
|
228
|
+
written.push(claudeMdPath);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const rulesDir = path.join(projectRoot, ".claude", "rules");
|
|
232
|
+
await ensureDir(rulesDir);
|
|
233
|
+
|
|
234
|
+
const artifactRulesPath = path.join(rulesDir, "artifact-format.md");
|
|
235
|
+
if (!(await fileExists(artifactRulesPath))) {
|
|
236
|
+
await fs.writeFile(artifactRulesPath, generateArtifactRules(lang ?? "en"), "utf8");
|
|
237
|
+
written.push(artifactRulesPath);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const briefRulesPath = path.join(rulesDir, "brief-rules.md");
|
|
241
|
+
if (!(await fileExists(briefRulesPath))) {
|
|
242
|
+
await fs.writeFile(briefRulesPath, generateBriefRules(), "utf8");
|
|
243
|
+
written.push(briefRulesPath);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
133
247
|
for (const commandName of commandNames) {
|
|
134
248
|
const templatePath = path.join(commandTemplatesDir, `${commandName}.md`);
|
|
135
249
|
if (!(await fileExists(templatePath))) {
|
package/src/cli/doctor.ts
CHANGED
|
@@ -137,8 +137,8 @@ export async function runDoctor(cwd: string, out: (line: string) => void): Promi
|
|
|
137
137
|
out("");
|
|
138
138
|
out(renderLogo(width));
|
|
139
139
|
out("");
|
|
140
|
-
out(center(color("Prodo — Product
|
|
141
|
-
out(center(color("
|
|
140
|
+
out(center(color("Prodo — AI-Powered Product Owner", "\u001B[1;37m"), width));
|
|
141
|
+
out(center(color("Built by Shahmarasy · Works with Claude, Codex, and Gemini", "\u001B[2;37m"), width));
|
|
142
142
|
out("");
|
|
143
143
|
out("Checking environment...");
|
|
144
144
|
out("");
|
package/src/cli/index.ts
CHANGED
|
@@ -120,7 +120,7 @@ export async function runCli(options: RunOptions = {}): Promise<number> {
|
|
|
120
120
|
|
|
121
121
|
program
|
|
122
122
|
.command("init [target]")
|
|
123
|
-
.option("--ai <name>", "agent
|
|
123
|
+
.option("--ai <name>", "agent: codex | gemini-cli | claude-cli")
|
|
124
124
|
.option("--lang <code>", "document language (e.g. en, tr)")
|
|
125
125
|
.option("--author <name>", "document author name")
|
|
126
126
|
.option("--preset <name>", "preset to install during initialization")
|
|
@@ -165,15 +165,14 @@ export async function runCli(options: RunOptions = {}): Promise<number> {
|
|
|
165
165
|
});
|
|
166
166
|
out(`Initialized Prodo scaffold at ${path.join(projectRoot, ".prodo")}`);
|
|
167
167
|
if (selectedAi) {
|
|
168
|
-
|
|
168
|
+
const label = selectedAi === "claude-cli" ? "Claude Code"
|
|
169
|
+
: selectedAi === "codex" ? "Codex" : "Gemini CLI";
|
|
170
|
+
out(`Agent commands installed for ${label}.`);
|
|
169
171
|
out(`Installed ${result.installedAgentFiles.length} command files.`);
|
|
170
|
-
out(
|
|
172
|
+
out(`Next: edit brief.md, open in ${label}, run /prodo-normalize`);
|
|
171
173
|
} else {
|
|
172
|
-
out("
|
|
174
|
+
out("Next: edit brief.md, then run `prodo generate`.");
|
|
173
175
|
}
|
|
174
|
-
out(`Settings file: ${result.settingsPath}`);
|
|
175
|
-
out(`Author: ${selected.author}`);
|
|
176
|
-
out("Next: edit brief.md.");
|
|
177
176
|
});
|
|
178
177
|
|
|
179
178
|
program
|
|
@@ -196,28 +195,30 @@ export async function runCli(options: RunOptions = {}): Promise<number> {
|
|
|
196
195
|
return;
|
|
197
196
|
}
|
|
198
197
|
await withBriefReadOnlyGuard(cwd, async () => {
|
|
199
|
-
await
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
198
|
+
const { createEngine, createPipelineState } = await import("../skill-engine");
|
|
199
|
+
const engine = await createEngine(cwd, out);
|
|
200
|
+
const state = createPipelineState(cwd);
|
|
201
|
+
const pipelineSkills = ["normalize", ...artifactTypes, "validate"];
|
|
203
202
|
|
|
204
|
-
|
|
205
|
-
await runArtifactCommand(type, { from: normalizedPath, agent: opts.agent }, cwd, out, {
|
|
206
|
-
suggestValidate: false
|
|
207
|
-
});
|
|
208
|
-
}
|
|
203
|
+
await runHookPhase(cwd, "before_normalize", out);
|
|
209
204
|
|
|
210
|
-
await
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
205
|
+
const finalState = await engine.runPipeline(pipelineSkills, state, {
|
|
206
|
+
log: (msg) => {
|
|
207
|
+
out(msg);
|
|
208
|
+
},
|
|
209
|
+
agent: opts.agent
|
|
214
210
|
});
|
|
215
|
-
|
|
216
|
-
|
|
211
|
+
|
|
212
|
+
await runHookPhase(cwd, "after_validate", out);
|
|
213
|
+
|
|
214
|
+
if (finalState.validationResult && !finalState.validationResult.pass) {
|
|
215
|
+
out(`Validation report written to: ${finalState.validationResult.reportPath}`);
|
|
217
216
|
throw new UserError("Validation failed. Review report and fix issues.");
|
|
218
217
|
}
|
|
218
|
+
if (finalState.validationResult) {
|
|
219
|
+
out(`Validation report written to: ${finalState.validationResult.reportPath}`);
|
|
220
|
+
}
|
|
219
221
|
out("Generation pipeline completed. Validation passed.");
|
|
220
|
-
await runHookPhase(cwd, "after_validate", out);
|
|
221
222
|
});
|
|
222
223
|
});
|
|
223
224
|
|
|
@@ -388,35 +389,38 @@ export async function runCli(options: RunOptions = {}): Promise<number> {
|
|
|
388
389
|
});
|
|
389
390
|
|
|
390
391
|
program
|
|
391
|
-
.command("skills"
|
|
392
|
-
.description("
|
|
392
|
+
.command("skills")
|
|
393
|
+
.description("Manage and run skills")
|
|
393
394
|
.argument("[action]", "list or run", "list")
|
|
394
395
|
.argument("[name]", "skill name (for run)")
|
|
395
396
|
.option("--input <json>", "JSON input for skill execution")
|
|
396
397
|
.action(async (action: string, name: string | undefined, opts: { input?: string }) => {
|
|
397
|
-
const {
|
|
398
|
-
const engine =
|
|
398
|
+
const { createEngine, createHydratedState } = await import("../skill-engine");
|
|
399
|
+
const engine = await createEngine(cwd, out);
|
|
400
|
+
const registry = engine.getRegistry();
|
|
399
401
|
|
|
400
402
|
if (action === "list") {
|
|
401
|
-
const manifests =
|
|
403
|
+
const manifests = registry.listManifests();
|
|
402
404
|
if (manifests.length === 0) {
|
|
403
405
|
out("No skills registered.");
|
|
404
406
|
return;
|
|
405
407
|
}
|
|
406
408
|
out("Available skills:\n");
|
|
407
409
|
for (const m of manifests) {
|
|
408
|
-
|
|
410
|
+
const deps = m.depends_on.length > 0 ? ` (deps: ${m.depends_on.join(", ")})` : "";
|
|
411
|
+
out(` ${m.name.padEnd(25)} [${m.category}] v${m.version} ${m.description}${deps}`);
|
|
409
412
|
}
|
|
410
413
|
return;
|
|
411
414
|
}
|
|
412
415
|
|
|
413
416
|
if (action === "run") {
|
|
414
417
|
if (!name) throw new UserError("Skill name is required. Usage: prodo skills run <name>");
|
|
418
|
+
const state = await createHydratedState(cwd);
|
|
415
419
|
const inputs = opts.input ? JSON.parse(opts.input) as Record<string, unknown> : {};
|
|
416
420
|
inputs.cwd = inputs.cwd ?? cwd;
|
|
417
|
-
const result = await engine.
|
|
421
|
+
const result = await engine.runSkill(name, state, { log: out });
|
|
418
422
|
out(`\nSkill "${name}" completed.`);
|
|
419
|
-
out(
|
|
423
|
+
out(`Completed skills: ${result.completedSkills.join(" → ")}`);
|
|
420
424
|
return;
|
|
421
425
|
}
|
|
422
426
|
|