pai-zero 0.13.2 → 0.14.1
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/bin/pai.js +1131 -192
- package/dist/bin/pai.js.map +1 -1
- package/dist/cli/index.js +1131 -192
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -593,8 +593,8 @@ function parseAiResult(result, analysis, projectName) {
|
|
|
593
593
|
async function interactiveInterview(analysis, _cwd, projectName) {
|
|
594
594
|
const { default: inquirer } = await import("inquirer");
|
|
595
595
|
const chalk9 = (await import("chalk")).default;
|
|
596
|
-
|
|
597
|
-
|
|
596
|
+
const Separator = inquirer.Separator;
|
|
597
|
+
step(1, TOTAL_STEPS, "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815");
|
|
598
598
|
console.log("");
|
|
599
599
|
const { mode } = await inquirer.prompt([{
|
|
600
600
|
type: "list",
|
|
@@ -602,48 +602,54 @@ async function interactiveInterview(analysis, _cwd, projectName) {
|
|
|
602
602
|
message: "\uC5B4\uB5A4 \uC218\uC900\uC758 \uD504\uB85C\uC81D\uD2B8\uC778\uAC00\uC694?",
|
|
603
603
|
choices: [
|
|
604
604
|
{
|
|
605
|
-
name: `\u{1F9EA} prototype ${colors.dim("\u2500 \uBE60\uB978 \uAC80\uC99D (\uC544\uC774\uB514\uC5B4
|
|
605
|
+
name: `\u{1F9EA} prototype ${colors.dim("\u2500 \uBE60\uB978 \uAC80\uC99D\uC6A9 (\uC544\uC774\uB514\uC5B4 \uD655\uC778 \xB7 \uD654\uBA74 \uC2DC\uC548 \xB7 \uAC04\uB2E8 \uD750\uB984)")}`,
|
|
606
606
|
value: "prototype"
|
|
607
607
|
},
|
|
608
608
|
{
|
|
609
|
-
name: `\u{1F52C} poc ${colors.dim("\u2500
|
|
609
|
+
name: `\u{1F52C} poc ${colors.dim("\u2500 \uB3D9\uC791 \uB370\uBAA8\uC6A9 (\uD575\uC2EC \uAE30\uB2A5 \uAD6C\uD604 \xB7 \uB0B4\uBD80 \uC2DC\uC5F0)")}`,
|
|
610
610
|
value: "poc"
|
|
611
611
|
},
|
|
612
612
|
{
|
|
613
|
-
name: `\u{1F3ED} production ${colors.dim("\u2500 \uC6B4\uC601 \uC11C\uBE44\uC2A4 (\uD14C\uC2A4\uD2B8\
|
|
613
|
+
name: `\u{1F3ED} production ${colors.dim("\u2500 \uC6B4\uC601 \uC11C\uBE44\uC2A4\uC6A9 (\uD14C\uC2A4\uD2B8 \xB7 QA \xB7 \uBC30\uD3EC \xB7 \uBAA8\uB2C8\uD130\uB9C1)")}`,
|
|
614
614
|
value: "production"
|
|
615
615
|
}
|
|
616
616
|
]
|
|
617
617
|
}]);
|
|
618
618
|
console.log("");
|
|
619
|
-
console.log(colors.dim(" \uC774 \uC218\uC900\uC5D0 \
|
|
619
|
+
console.log(colors.dim(" \uC774 \uC218\uC900\uC5D0 \uAE30\uBCF8 \uD3EC\uD568\uB418\uB294 \uD56D\uBAA9"));
|
|
620
620
|
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
};
|
|
626
|
-
|
|
627
|
-
console.log(` ${colors.
|
|
621
|
+
console.log(` ${colors.success("\u2713")} GitHub \uD504\uB85C\uC81D\uD2B8 \uAD6C\uC870`);
|
|
622
|
+
console.log(` ${colors.success("\u2713")} PRD \uC124\uACC4 \uD15C\uD50C\uB9BF (OpenSpec)`);
|
|
623
|
+
console.log(` ${colors.success("\u2713")} AI \uCEE8\uD14D\uC2A4\uD2B8 (CLAUDE.md)`);
|
|
624
|
+
console.log(` ${colors.success("\u2713")} /pai \uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC`);
|
|
625
|
+
console.log(` ${colors.success("\u2713")} \uD488\uC9C8 \uC9C4\uB2E8 (Vibe-Ready)`);
|
|
626
|
+
if (mode === "poc" || mode === "production") {
|
|
627
|
+
console.log(` ${colors.accent("+")} OMC \uC5D0\uC774\uC804\uD2B8 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158`);
|
|
628
628
|
}
|
|
629
|
-
|
|
630
|
-
|
|
629
|
+
if (mode === "production") {
|
|
630
|
+
console.log(` ${colors.accent("+")} gstack \uD14C\uC2A4\uD2B8 \xB7 Harness \uD488\uC9C8 \uAC80\uC99D`);
|
|
631
|
+
}
|
|
632
|
+
console.log(colors.dim(" + \uCD94\uAC00 \uAE30\uB2A5\uC740 \uB2E4\uC74C \uB2E8\uACC4\uC5D0\uC11C \uC120\uD0DD"));
|
|
633
|
+
console.log("");
|
|
634
|
+
step(2, TOTAL_STEPS, "\uCD94\uAC00 \uAE30\uB2A5 \uC120\uD0DD");
|
|
631
635
|
console.log("");
|
|
636
|
+
console.log(colors.dim(" \uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uD544\uC694\uD55C \uAE30\uB2A5\uB9CC \uC120\uD0DD\uD569\uB2C8\uB2E4."));
|
|
637
|
+
console.log(colors.dim(" \uC9C0\uAE08 \uC815\uD558\uC9C0 \uC54A\uC544\uB3C4 \uB098\uC911\uC5D0 \uB2E4\uC2DC \uC124\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
|
|
638
|
+
console.log("");
|
|
639
|
+
let authMethods = [];
|
|
640
|
+
let customAuth;
|
|
632
641
|
const { needAuth } = await inquirer.prompt([{
|
|
633
642
|
type: "list",
|
|
634
643
|
name: "needAuth",
|
|
635
|
-
message:
|
|
644
|
+
message: `\uB85C\uADF8\uC778 \uAE30\uB2A5\uC774 \uD544\uC694\uD55C\uAC00\uC694? ${colors.dim("\u2500 \uC0AC\uC6A9\uC790\uB97C \uAD6C\uBD84\uD574\uC57C \uD560 \uB54C \uD544\uC694")}`,
|
|
636
645
|
choices: [
|
|
637
|
-
{ name: "\
|
|
646
|
+
{ name: "\uB098\uC911\uC5D0 \uC124\uC815", value: false },
|
|
638
647
|
{ name: "\uB124, \uC124\uC815\uD560\uAC8C\uC694", value: true }
|
|
639
648
|
]
|
|
640
649
|
}]);
|
|
641
|
-
let authMethods = [];
|
|
642
|
-
let customAuth;
|
|
643
650
|
if (needAuth) {
|
|
644
651
|
console.log("");
|
|
645
652
|
hint("Space\uB85C \uC120\uD0DD \xB7 \uC120\uD0DD \uD6C4 Enter\uB85C \uC644\uB8CC");
|
|
646
|
-
const Separator = inquirer.Separator;
|
|
647
653
|
const { auths } = await inquirer.prompt([{
|
|
648
654
|
type: "checkbox",
|
|
649
655
|
name: "auths",
|
|
@@ -683,55 +689,48 @@ async function interactiveInterview(analysis, _cwd, projectName) {
|
|
|
683
689
|
}
|
|
684
690
|
}
|
|
685
691
|
}
|
|
686
|
-
let extraTools = [];
|
|
687
|
-
if (mode === "poc" || mode === "production") {
|
|
688
|
-
extraTools.push("omc");
|
|
689
|
-
}
|
|
690
|
-
if (mode === "poc" || mode === "production") {
|
|
691
|
-
console.log("");
|
|
692
|
-
const { hosting } = await inquirer.prompt([{
|
|
693
|
-
type: "list",
|
|
694
|
-
name: "hosting",
|
|
695
|
-
message: "\uBC30\uD3EC \uD658\uACBD:",
|
|
696
|
-
choices: [
|
|
697
|
-
{ name: `Vercel ${colors.dim("\u2500 \uC790\uB3D9 \uBC30\uD3EC (Preview + Production)")}`, value: "vercel" },
|
|
698
|
-
{ name: `\uB2E4\uB978 \uC11C\uBE44\uC2A4 \uC0AC\uC6A9 ${colors.dim("\u2500 AWS, GCP, Netlify \uB4F1)")}`, value: "other" },
|
|
699
|
-
{ name: `\uB098\uC911\uC5D0 \uC815\uD558\uAE30 ${colors.dim("\u2500 \uBC30\uD3EC \uC124\uC815 \uC5C6\uC774 \uC2DC\uC791")}`, value: "skip" }
|
|
700
|
-
]
|
|
701
|
-
}]);
|
|
702
|
-
if (hosting === "vercel") extraTools.push("vercel");
|
|
703
|
-
}
|
|
704
|
-
if (mode === "production") {
|
|
705
|
-
console.log("");
|
|
706
|
-
const { database } = await inquirer.prompt([{
|
|
707
|
-
type: "list",
|
|
708
|
-
name: "database",
|
|
709
|
-
message: "\uB370\uC774\uD130\uBCA0\uC774\uC2A4:",
|
|
710
|
-
choices: [
|
|
711
|
-
{ name: `Supabase ${colors.dim("\u2500 PostgreSQL + Auth + Storage")}`, value: "supabase" },
|
|
712
|
-
{ name: `\uB2E4\uB978 \uC11C\uBE44\uC2A4 \uC0AC\uC6A9 ${colors.dim("\u2500 PlanetScale, Neon, Firebase \uB4F1")}`, value: "other" },
|
|
713
|
-
{ name: `\uB098\uC911\uC5D0 \uC815\uD558\uAE30 ${colors.dim("\u2500 DB \uC124\uC815 \uC5C6\uC774 \uC2DC\uC791")}`, value: "skip" }
|
|
714
|
-
]
|
|
715
|
-
}]);
|
|
716
|
-
if (database === "supabase") extraTools.push("supabase");
|
|
717
|
-
extraTools.push("gstack", "harness");
|
|
718
|
-
}
|
|
719
|
-
let mcp;
|
|
720
692
|
console.log("");
|
|
693
|
+
let hostingChoice = "skip";
|
|
694
|
+
const { hosting } = await inquirer.prompt([{
|
|
695
|
+
type: "list",
|
|
696
|
+
name: "hosting",
|
|
697
|
+
message: `\uBC30\uD3EC \uD658\uACBD\uC744 \uC124\uC815\uD560\uAE4C\uC694? ${colors.dim("\u2500 \uB9CC\uB4E0 \uC11C\uBE44\uC2A4\uB97C \uC778\uD130\uB137\uC5D0 \uACF5\uAC1C\uD560 \uB54C \uD544\uC694")}`,
|
|
698
|
+
choices: [
|
|
699
|
+
{ name: "\uB098\uC911\uC5D0 \uC124\uC815", value: "skip" },
|
|
700
|
+
{ name: `Vercel ${colors.dim("\u2500 \uC790\uB3D9 \uBC30\uD3EC (Preview + Production)")}`, value: "vercel" },
|
|
701
|
+
{ name: `\uB2E4\uB978 \uC11C\uBE44\uC2A4 \uC0AC\uC6A9 ${colors.dim("\u2500 AWS, GCP, Netlify \uB4F1")}`, value: "other" }
|
|
702
|
+
]
|
|
703
|
+
}]);
|
|
704
|
+
hostingChoice = hosting;
|
|
705
|
+
console.log("");
|
|
706
|
+
let dbChoice = "skip";
|
|
707
|
+
const { database } = await inquirer.prompt([{
|
|
708
|
+
type: "list",
|
|
709
|
+
name: "database",
|
|
710
|
+
message: `\uB370\uC774\uD130\uBCA0\uC774\uC2A4\uAC00 \uD544\uC694\uD55C\uAC00\uC694? ${colors.dim("\u2500 \uC0AC\uC6A9\uC790 \uB370\uC774\uD130\uB97C \uC800\uC7A5\uD560 \uB54C \uD544\uC694")}`,
|
|
711
|
+
choices: [
|
|
712
|
+
{ name: "\uB098\uC911\uC5D0 \uC124\uC815", value: "skip" },
|
|
713
|
+
{ name: `Supabase ${colors.dim("\u2500 PostgreSQL + Auth + Storage")}`, value: "supabase" },
|
|
714
|
+
{ name: `\uB2E4\uB978 \uC11C\uBE44\uC2A4 \uC0AC\uC6A9 ${colors.dim("\u2500 PlanetScale, Neon, Firebase \uB4F1")}`, value: "other" }
|
|
715
|
+
]
|
|
716
|
+
}]);
|
|
717
|
+
dbChoice = database;
|
|
718
|
+
console.log("");
|
|
719
|
+
let mcp;
|
|
720
|
+
let mcpEnabled = false;
|
|
721
721
|
const { mcpNeed } = await inquirer.prompt([{
|
|
722
722
|
type: "list",
|
|
723
723
|
name: "mcpNeed",
|
|
724
|
-
message:
|
|
724
|
+
message: `MCP \uC11C\uBC84\uAC00 \uD544\uC694\uD55C\uAC00\uC694? ${colors.dim("\u2500 Claude\uAC00 \uD638\uCD9C\uD560 \uCEE4\uC2A4\uD140 \uB3C4\uAD6C\uB97C \uB9CC\uB4E4 \uB54C \uD544\uC694")}`,
|
|
725
725
|
choices: [
|
|
726
|
-
{ name:
|
|
727
|
-
{ name:
|
|
728
|
-
]
|
|
729
|
-
default: "yes"
|
|
726
|
+
{ name: "\uB098\uC911\uC5D0 \uC124\uC815", value: "no" },
|
|
727
|
+
{ name: `\uB124, \uC124\uC815\uD560\uAC8C\uC694 ${colors.dim("\u2500 \uB3C4\uAD6C/\uB9AC\uC18C\uC2A4/\uD504\uB86C\uD504\uD2B8 \uC11C\uBC84 \uC0DD\uC131")}`, value: "yes" }
|
|
728
|
+
]
|
|
730
729
|
}]);
|
|
731
|
-
|
|
732
|
-
|
|
730
|
+
if (mcpNeed === "yes") {
|
|
731
|
+
mcpEnabled = true;
|
|
733
732
|
console.log("");
|
|
734
|
-
const hasSupabase =
|
|
733
|
+
const hasSupabase = dbChoice === "supabase";
|
|
735
734
|
const typeChoices = [
|
|
736
735
|
{ name: `Tools \uC11C\uBC84 (\uAC1C\uC778\uC6A9) ${colors.dim("\u2500 AI\uAC00 \uD638\uCD9C\uD560 \uAE30\uB2A5 \uC81C\uACF5")}`, value: "tools" }
|
|
737
736
|
];
|
|
@@ -764,48 +763,67 @@ async function interactiveInterview(analysis, _cwd, projectName) {
|
|
|
764
763
|
type: mcpType,
|
|
765
764
|
name: mcpName.trim()
|
|
766
765
|
};
|
|
767
|
-
extraTools.push("mcp");
|
|
768
766
|
}
|
|
769
767
|
const { RECIPES: RECIPES2 } = await Promise.resolve().then(() => (init_recipes(), recipes_exports));
|
|
770
768
|
const recipeKeys = Object.keys(RECIPES2);
|
|
771
769
|
let selectedRecipes = [];
|
|
772
770
|
if (recipeKeys.length > 0) {
|
|
773
771
|
console.log("");
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
name: "recipes",
|
|
779
|
-
message: "\uC0AC\uB0B4 \uC2DC\uC2A4\uD15C \uC5F0\uB3D9 \uB808\uC2DC\uD53C\uB97C \uC124\uCE58\uD560\uAE4C\uC694?",
|
|
772
|
+
const { needInternal } = await inquirer.prompt([{
|
|
773
|
+
type: "list",
|
|
774
|
+
name: "needInternal",
|
|
775
|
+
message: `\uC0AC\uB0B4 \uC2DC\uC2A4\uD15C\uACFC \uC5F0\uACB0\uC774 \uD544\uC694\uD55C\uAC00\uC694? ${colors.dim("\u2500 \uC0AC\uB0B4 \uC778\uC99D, \uBA54\uC2E0\uC800 \uB4F1 \uB0B4\uBD80 \uC11C\uBE44\uC2A4 \uC5F0\uB3D9")}`,
|
|
780
776
|
choices: [
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
value: k
|
|
784
|
-
})),
|
|
785
|
-
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
786
|
-
{ name: chalk9.green("\u21B5 \uC120\uD0DD \uC644\uB8CC (\uAC74\uB108\uB6F0\uAE30)"), value: "__done__" }
|
|
777
|
+
{ name: "\uC544\uB2C8\uC694", value: false },
|
|
778
|
+
{ name: "\uB124, \uC120\uD0DD\uD560\uAC8C\uC694", value: true }
|
|
787
779
|
]
|
|
788
780
|
}]);
|
|
789
|
-
|
|
781
|
+
if (needInternal) {
|
|
782
|
+
console.log("");
|
|
783
|
+
hint("Space\uB85C \uC120\uD0DD \xB7 \uC120\uD0DD \uD6C4 Enter\uB85C \uC644\uB8CC");
|
|
784
|
+
const recipeAnswer = await inquirer.prompt([{
|
|
785
|
+
type: "checkbox",
|
|
786
|
+
name: "recipes",
|
|
787
|
+
message: "\uC0AC\uB0B4 \uC5F0\uB3D9 \uB808\uC2DC\uD53C\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
788
|
+
choices: [
|
|
789
|
+
...recipeKeys.map((k) => ({
|
|
790
|
+
name: `${RECIPES2[k].label} ${colors.dim("\u2500 " + RECIPES2[k].description)}`,
|
|
791
|
+
value: k
|
|
792
|
+
})),
|
|
793
|
+
new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
794
|
+
{ name: chalk9.green("\u21B5 \uC120\uD0DD \uC644\uB8CC"), value: "__done__" }
|
|
795
|
+
]
|
|
796
|
+
}]);
|
|
797
|
+
selectedRecipes = recipeAnswer.recipes.filter((x) => x !== "__done__");
|
|
798
|
+
}
|
|
790
799
|
}
|
|
791
|
-
|
|
800
|
+
const extraTools = [];
|
|
801
|
+
if (mode === "poc" || mode === "production") extraTools.push("omc");
|
|
802
|
+
if (mode === "production") extraTools.push("gstack", "harness");
|
|
803
|
+
if (hostingChoice === "vercel") extraTools.push("vercel");
|
|
804
|
+
if (dbChoice === "supabase") extraTools.push("supabase");
|
|
805
|
+
if (mcpEnabled) extraTools.push("mcp");
|
|
806
|
+
console.log("");
|
|
807
|
+
step(3, TOTAL_STEPS, "\uC124\uCE58 \uD655\uC778");
|
|
808
|
+
console.log("");
|
|
809
|
+
console.log(colors.dim(" \uC544\uB798 \uC124\uC815\uC73C\uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uC900\uBE44\uD569\uB2C8\uB2E4."));
|
|
792
810
|
console.log("");
|
|
793
|
-
console.log(colors.dim(" \uC124\uCE58\uB420 \
|
|
811
|
+
console.log(colors.dim(" \uC124\uCE58\uB420 \uAE30\uB2A5"));
|
|
794
812
|
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
795
|
-
console.log(` ${colors.success("\u2713")} GitHub \uD504\uB85C\uC81D\uD2B8 \uAD6C\uC870`);
|
|
796
|
-
console.log(` ${colors.success("\u2713")} PRD \uC124\uACC4 \uD15C\uD50C\uB9BF`);
|
|
797
|
-
console.log(` ${colors.success("\u2713")} AI \
|
|
798
|
-
console.log(` ${colors.success("\u2713")}
|
|
799
|
-
console.log(` ${colors.success("\u2713")}
|
|
813
|
+
console.log(` ${colors.success("\u2713")} GitHub \uD504\uB85C\uC81D\uD2B8 \uAD6C\uC870 ${colors.dim("\u2500 \uD3F4\uB354 \xB7 \uBE0C\uB79C\uCE58 \xB7 .gitignore \uAE30\uBCF8 \uBF08\uB300")}`);
|
|
814
|
+
console.log(` ${colors.success("\u2713")} PRD \uC124\uACC4 \uD15C\uD50C\uB9BF ${colors.dim("\u2500 \uC694\uAD6C\uC0AC\uD56D\uC744 AI\uAC00 \uC774\uD574\uD558\uB294 \uD615\uC2DD\uC73C\uB85C \uC815\uB9AC")}`);
|
|
815
|
+
console.log(` ${colors.success("\u2713")} AI \uCEE8\uD14D\uC2A4\uD2B8 (CLAUDE.md) ${colors.dim("\u2500 Claude \uC791\uC5C5 \uAE30\uC900 \uBB38\uC11C")}`);
|
|
816
|
+
console.log(` ${colors.success("\u2713")} /pai \uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC ${colors.dim("\u2500 \uC124\uACC4\xB7\uAC80\uC99D\xB7\uD3C9\uAC00\uB97C Claude \uC548\uC5D0\uC11C \uC2E4\uD589")}`);
|
|
817
|
+
console.log(` ${colors.success("\u2713")} Harness \uD488\uC9C8 \uC815\uCC45 ${colors.dim("\u2500 hook \xB7 CI \xB7 lint \uADDC\uCE59 \uC790\uB3D9 \uAD6C\uC131")}`);
|
|
800
818
|
if (extraTools.length > 0) {
|
|
801
819
|
console.log("");
|
|
802
|
-
if (extraTools.includes("omc")) console.log(` ${colors.accent("+")} OMC
|
|
803
|
-
if (extraTools.includes("vercel")) console.log(` ${colors.accent("+")} Vercel
|
|
804
|
-
if (extraTools.includes("supabase")) console.log(` ${colors.accent("+")} Supabase
|
|
805
|
-
if (extraTools.includes("gstack")) console.log(` ${colors.accent("+")} gstack
|
|
806
|
-
if (extraTools.includes("harness")) console.log(` ${colors.accent("+")} Harness
|
|
820
|
+
if (extraTools.includes("omc")) console.log(` ${colors.accent("+")} OMC ${colors.dim("\u2500 AI \uC5D0\uC774\uC804\uD2B8 \uC791\uC5C5 \uBD84\uC5C5")}`);
|
|
821
|
+
if (extraTools.includes("vercel")) console.log(` ${colors.accent("+")} Vercel ${colors.dim("\u2500 \uC790\uB3D9 \uBC30\uD3EC (Preview + Production)")}`);
|
|
822
|
+
if (extraTools.includes("supabase")) console.log(` ${colors.accent("+")} Supabase ${colors.dim("\u2500 PostgreSQL + Auth + Storage")}`);
|
|
823
|
+
if (extraTools.includes("gstack")) console.log(` ${colors.accent("+")} gstack ${colors.dim("\u2500 \uD14C\uC2A4\uD2B8 \uC790\uB3D9\uD654")}`);
|
|
824
|
+
if (extraTools.includes("harness")) console.log(` ${colors.accent("+")} Harness ${colors.dim("\u2500 \uD488\uC9C8 \uAC80\uC99D")}`);
|
|
807
825
|
if (extraTools.includes("mcp") && mcp) {
|
|
808
|
-
console.log(` ${colors.accent("+")} MCP \uC11C\uBC84 (${mcp.name}
|
|
826
|
+
console.log(` ${colors.accent("+")} MCP \uC11C\uBC84 (${mcp.name}) ${colors.dim("\u2500 " + mcp.type)}`);
|
|
809
827
|
}
|
|
810
828
|
}
|
|
811
829
|
if (authMethods.length > 0) {
|
|
@@ -814,7 +832,29 @@ async function interactiveInterview(analysis, _cwd, projectName) {
|
|
|
814
832
|
}
|
|
815
833
|
if (selectedRecipes.length > 0) {
|
|
816
834
|
console.log("");
|
|
817
|
-
console.log(` ${colors.accent("+")} \uC0AC\uB0B4 \uC5F0\uB3D9
|
|
835
|
+
console.log(` ${colors.accent("+")} \uC0AC\uB0B4 \uC5F0\uB3D9: ${selectedRecipes.join(", ")}`);
|
|
836
|
+
}
|
|
837
|
+
console.log("");
|
|
838
|
+
console.log(colors.dim(" \uC0DD\uC131\uB420 \uD575\uC2EC \uD30C\uC77C"));
|
|
839
|
+
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
840
|
+
const expectedFiles = [
|
|
841
|
+
"CLAUDE.md",
|
|
842
|
+
".claude/settings.json",
|
|
843
|
+
".harness.json",
|
|
844
|
+
"docs/openspec.md",
|
|
845
|
+
".pai/config.json"
|
|
846
|
+
];
|
|
847
|
+
if (authMethods.length > 0 || extraTools.includes("supabase") || extraTools.includes("vercel")) {
|
|
848
|
+
expectedFiles.push(".env.local");
|
|
849
|
+
}
|
|
850
|
+
if (extraTools.includes("omc")) expectedFiles.push(".pai/omc.md");
|
|
851
|
+
if (extraTools.includes("vercel")) expectedFiles.push("vercel.json");
|
|
852
|
+
if (extraTools.includes("mcp") && mcp) expectedFiles.push(`mcp-server/${mcp.name}/`);
|
|
853
|
+
if (selectedRecipes.length > 0) {
|
|
854
|
+
for (const r of selectedRecipes) expectedFiles.push(`docs/recipes/${r}/`);
|
|
855
|
+
}
|
|
856
|
+
for (const f of expectedFiles) {
|
|
857
|
+
console.log(` ${colors.dim("\xB7")} ${f}`);
|
|
818
858
|
}
|
|
819
859
|
console.log("");
|
|
820
860
|
const { proceed } = await inquirer.prompt([{
|
|
@@ -852,12 +892,14 @@ async function interactiveInterview(analysis, _cwd, projectName) {
|
|
|
852
892
|
autoGenerated: false
|
|
853
893
|
};
|
|
854
894
|
}
|
|
895
|
+
var TOTAL_STEPS;
|
|
855
896
|
var init_interviewer = __esm({
|
|
856
897
|
"src/stages/environment/interviewer.ts"() {
|
|
857
898
|
"use strict";
|
|
858
899
|
init_ui();
|
|
859
900
|
init_logger();
|
|
860
901
|
init_config();
|
|
902
|
+
TOTAL_STEPS = 4;
|
|
861
903
|
}
|
|
862
904
|
});
|
|
863
905
|
|
|
@@ -2844,17 +2886,65 @@ description: "Ideation \u2192 PRD \u2192 \uD558\uB124\uC2A4 \uAD6C\uC131\uAE4C\u
|
|
|
2844
2886
|
|
|
2845
2887
|
# PAI \uC124\uACC4 \u2014 \uC2EC\uCE35 \uC778\uD130\uBDF0 \uAE30\uBC18 \uD30C\uC774\uD504\uB77C\uC778
|
|
2846
2888
|
|
|
2889
|
+
## \uC138\uC158 \uAD00\uB9AC \uC6D0\uCE59 (\uBC18\uB4DC\uC2DC \uC900\uC218)
|
|
2890
|
+
|
|
2891
|
+
### Phase \uC804\uD658 \uCCB4\uD06C\uD3EC\uC778\uD2B8
|
|
2892
|
+
\uAC01 Phase\uAC00 \uC644\uB8CC\uB420 \uB54C\uB9C8\uB2E4 \uB2E4\uC74C \uC21C\uC11C\uB85C **\uBC18\uB4DC\uC2DC** \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD655\uC778\uD558\uC138\uC694:
|
|
2893
|
+
|
|
2894
|
+
\`\`\`
|
|
2895
|
+
\u2705 Phase N \uC644\uB8CC.
|
|
2896
|
+
|
|
2897
|
+
[1] git commit \uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?
|
|
2898
|
+
\u2192 \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uC81C\uC548: "docs: ..."
|
|
2899
|
+
[2] \uB2E4\uC74C Phase(N+1)\uB85C \uB118\uC5B4\uAC00\uAE30 \uC804\uC5D0 /clear \uB85C \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uCD08\uAE30\uD654\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?
|
|
2900
|
+
\u2192 \uCD08\uAE30\uD654\uD558\uBA74 \uD604\uC7AC \uB300\uD654\uB294 \uC0AC\uB77C\uC9C0\uC9C0\uB9CC \uD30C\uC77C\uC740 \uC720\uC9C0\uB429\uB2C8\uB2E4.
|
|
2901
|
+
\u2192 \uCD08\uAE30\uD654 \uD6C4 /pai design prd \uB610\uB294 /pai design validate \uB85C \uC7AC\uC2DC\uC791\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.
|
|
2902
|
+
\`\`\`
|
|
2903
|
+
|
|
2904
|
+
\uC0AC\uC6A9\uC790\uAC00 **\uB458 \uB2E4 \uC6D0\uD558\uC9C0 \uC54A\uB294\uB2E4\uBA74** \uBC14\uB85C \uB2E4\uC74C Phase\uB97C \uC9C4\uD589\uD558\uC138\uC694.
|
|
2905
|
+
|
|
2906
|
+
### \uCEE8\uD14D\uC2A4\uD2B8 \uD06C\uAE30 \uACBD\uACE0
|
|
2907
|
+
Phase \uC9C4\uD589 \uC911 \uB300\uD654\uAC00 \uAE38\uC5B4\uC84C\uB2E4\uACE0 \uD310\uB2E8\uB420 \uB54C (\uBA54\uC2DC\uC9C0\uAC00 10\uAC1C \uC774\uC0C1\uC774\uAC70\uB098 \uC778\uD130\uBDF0\uAC00 \uBC18\uBCF5\uB420 \uB54C):
|
|
2908
|
+
|
|
2909
|
+
\`\`\`
|
|
2910
|
+
\u26A0\uFE0F \uB300\uD654\uAC00 \uAE38\uC5B4\uC84C\uC2B5\uB2C8\uB2E4. /compact \uB85C \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uC555\uCD95\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?
|
|
2911
|
+
\u2192 \uC555\uCD95\uD574\uB3C4 \uC791\uC5C5 \uD750\uB984\uC740 \uC720\uC9C0\uB429\uB2C8\uB2E4.
|
|
2912
|
+
\`\`\`
|
|
2913
|
+
|
|
2914
|
+
> \uC774 \uB450 \uAC00\uC9C0 \uD655\uC778\uC740 \uC0AC\uC6A9\uC790\uAC00 \uB9E5\uB77D \uAD00\uB9AC\uB97C \uB193\uCE58\uC9C0 \uC54A\uB3C4\uB85D \uB3D5\uB294 \uD575\uC2EC \uC9C0\uCE68\uC785\uB2C8\uB2E4. \uC808\uB300 \uC0DD\uB7B5\uD558\uC9C0 \uB9C8\uC138\uC694.
|
|
2915
|
+
|
|
2847
2916
|
## \uC778\uC790: $ARGUMENTS
|
|
2848
|
-
- \uC778\uC790 \uC5C6\uC74C \u2192 \uC804\uCCB4 \uD30C\uC774\uD504\uB77C\uC778 (Ideation \u2192 PRD \u2192 \uD558\uB124\uC2A4)
|
|
2917
|
+
- \uC778\uC790 \uC5C6\uC74C \u2192 \uC804\uCCB4 \uD30C\uC774\uD504\uB77C\uC778 (Git Init \u2192 Ideation \u2192 PRD \u2192 \uD558\uB124\uC2A4)
|
|
2849
2918
|
- \`ideation\` \u2192 Ideation\uB9CC \uC218\uD589
|
|
2850
2919
|
- \`prd\` \u2192 PRD\uB9CC \uC218\uD589
|
|
2851
2920
|
- \`validate\` \u2192 \uAC80\uC99D\uB9CC \uC218\uD589
|
|
2852
2921
|
|
|
2922
|
+
---
|
|
2923
|
+
|
|
2924
|
+
## Phase 0: Git \uC800\uC7A5\uC18C \uCD08\uAE30\uD654
|
|
2925
|
+
|
|
2926
|
+
**\uBAA9\uD45C**: \uC124\uACC4 \uC0B0\uCD9C\uBB3C\uC744 \uBC84\uC804 \uAD00\uB9AC \uC544\uB798\uC5D0 \uB450\uAE30
|
|
2927
|
+
|
|
2928
|
+
1. \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0 \`.git\`\uC774 \uC788\uB294\uC9C0 \uD655\uC778 (\`git status\`)
|
|
2929
|
+
2. \`.git\`\uC774 \uC5C6\uC73C\uBA74:
|
|
2930
|
+
\`\`\`bash
|
|
2931
|
+
git init
|
|
2932
|
+
git add .
|
|
2933
|
+
git commit -m "chore: initial commit"
|
|
2934
|
+
\`\`\`
|
|
2935
|
+
3. \`.git\`\uC774 \uC774\uBBF8 \uC788\uC73C\uBA74 \uAC74\uB108\uB6F0\uACE0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC54C\uB9BC
|
|
2936
|
+
|
|
2937
|
+
> \uC774 Phase\uB294 \uC778\uC790\uC640 \uBB34\uAD00\uD558\uAC8C **\uD56D\uC0C1 \uBA3C\uC800 \uC2E4\uD589**\uD558\uC138\uC694.
|
|
2938
|
+
|
|
2939
|
+
---
|
|
2940
|
+
|
|
2853
2941
|
## Phase 1: Ideation (\uC544\uC774\uB514\uC5B4 \uAD6C\uCCB4\uD654)
|
|
2854
2942
|
|
|
2855
2943
|
**\uBAA9\uD45C**: \uC0AC\uC6A9\uC790\uC758 \uBA38\uB9BF\uC18D \uC544\uC774\uB514\uC5B4\uB97C \`docs/ideation.md\`\uB85C \uAD6C\uCCB4\uD654
|
|
2856
2944
|
|
|
2857
|
-
|
|
2945
|
+
\`docs/ideation.md\`\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD558\uBA74 \uB0B4\uC6A9\uC744 \uC77D\uACE0 Phase 2\uB85C \uB118\uC5B4\uAC00\uC138\uC694.
|
|
2946
|
+
|
|
2947
|
+
\uC5C6\uC73C\uBA74 AskUserQuestion \uB3C4\uAD6C\uB85C \uC2EC\uCE35 \uC778\uD130\uBDF0\uB97C \uC9C4\uD589\uD558\uC138\uC694:
|
|
2858
2948
|
1. "\uC5B4\uB5A4 \uBB38\uC81C\uB97C \uD574\uACB0\uD558\uACE0 \uC2F6\uC73C\uC138\uC694?" \u2014 \uD575\uC2EC \uACFC\uC81C \uD30C\uC545
|
|
2859
2949
|
2. "\uC774 \uC11C\uBE44\uC2A4\uB97C \uC8FC\uB85C \uC0AC\uC6A9\uD560 \uC0AC\uB78C\uC740 \uB204\uAD6C\uC778\uAC00\uC694?" \u2014 \uB300\uC0C1 \uC0AC\uC6A9\uC790
|
|
2860
2950
|
3. "\uBE44\uC2B7\uD55C \uC11C\uBE44\uC2A4\uAC00 \uC788\uB2E4\uBA74 \uBB50\uAC00 \uB2E4\uB978\uAC00\uC694?" \u2014 \uCC28\uBCC4\uC810
|
|
@@ -2863,6 +2953,14 @@ AskUserQuestion \uB3C4\uAD6C\uB85C \uC2EC\uCE35 \uC778\uD130\uBDF0\uB97C \uC9C4\
|
|
|
2863
2953
|
|
|
2864
2954
|
\uB2F5\uBCC0\uC744 \uC885\uD569\uD558\uC5EC \`docs/ideation.md\`\uB97C \uC0DD\uC131\uD558\uC138\uC694.
|
|
2865
2955
|
|
|
2956
|
+
**Phase 1 \uC644\uB8CC \uD6C4 \uCEE4\uBC0B**:
|
|
2957
|
+
\`\`\`bash
|
|
2958
|
+
git add docs/ideation.md
|
|
2959
|
+
git commit -m "docs: add ideation \u2014 [\uD504\uB85C\uC81D\uD2B8 \uD55C \uC904 \uC124\uBA85]"
|
|
2960
|
+
\`\`\`
|
|
2961
|
+
|
|
2962
|
+
---
|
|
2963
|
+
|
|
2866
2964
|
## Phase 2: PRD \uC791\uC131 (\uC81C\uD488 \uC694\uAD6C\uC0AC\uD56D)
|
|
2867
2965
|
|
|
2868
2966
|
**\uBAA9\uD45C**: Ideation\uC744 \uAE30\uBC18\uC73C\uB85C \`docs/openspec.md\` PRD \uC791\uC131
|
|
@@ -2880,6 +2978,12 @@ AskUserQuestion \uB3C4\uAD6C\uB85C \uC2EC\uCE35 \uC778\uD130\uBDF0\uB97C \uC9C4\
|
|
|
2880
2978
|
- \uAE30\uC220 \uC2A4\uD0DD (Stack)
|
|
2881
2979
|
- API \uC5D4\uB4DC\uD3EC\uC778\uD2B8
|
|
2882
2980
|
|
|
2981
|
+
**Phase 2 \uC644\uB8CC \uD6C4 \uCEE4\uBC0B**:
|
|
2982
|
+
\`\`\`bash
|
|
2983
|
+
git add docs/openspec.md
|
|
2984
|
+
git commit -m "docs: add openspec PRD \u2014 [\uD504\uB85C\uC81D\uD2B8\uBA85]"
|
|
2985
|
+
\`\`\`
|
|
2986
|
+
|
|
2883
2987
|
## Phase 2.5: MCP \uC11C\uBC84 \uC124\uACC4 (MCP \uD504\uB85C\uC81D\uD2B8\uB9CC)
|
|
2884
2988
|
|
|
2885
2989
|
\\\`.pai/config.json\\\`\uC758 \\\`mcp.enabled\\\`\uAC00 true\uC774\uBA74 \uC774 Phase\uB97C \uC218\uD589\uD558\uC138\uC694:
|
|
@@ -2926,18 +3030,25 @@ AskUserQuestion\uC73C\uB85C \uAE30\uC220\uC801 \uC81C\uC57D \uD30C\uC545:
|
|
|
2926
3030
|
- \uD14C\uC2A4\uD2B8 \uD658\uACBD \u2014 \`tests/\` \uB514\uB809\uD1A0\uB9AC + \uD14C\uC2A4\uD2B8 \uD504\uB808\uC784\uC6CC\uD06C \uC874\uC7AC \uD655\uC778
|
|
2927
3031
|
- \uBD80\uC871\uD55C \uD56D\uBAA9\uC740 \uC790\uB3D9 \uC0DD\uC131 \uC81C\uC548
|
|
2928
3032
|
|
|
2929
|
-
## Phase 4: \uAC80\uC99D
|
|
3033
|
+
## Phase 4: \uAC80\uC99D + \uCD5C\uC885 \uCEE4\uBC0B
|
|
2930
3034
|
|
|
2931
3035
|
\uBAA8\uB4E0 Phase \uC644\uB8CC \uD6C4:
|
|
2932
3036
|
1. \`docs/ideation.md\` \uC874\uC7AC + \uB0B4\uC6A9 \uD655\uC778
|
|
2933
3037
|
2. \`docs/openspec.md\` 5\uAC1C \uC139\uC158 \uC644\uC131\uB3C4 \uAC80\uC99D
|
|
2934
3038
|
3. \`handoff.md\` \uC5C5\uB370\uC774\uD2B8 \u2014 \uC644\uB8CC\uB41C \uD56D\uBAA9 \uCCB4\uD06C, \uB2E4\uC74C \uB2E8\uACC4 \uAC31\uC2E0
|
|
2935
3039
|
|
|
3040
|
+
**\uCD5C\uC885 \uCEE4\uBC0B** (\uBCC0\uACBD\uC0AC\uD56D\uC774 \uC788\uC744 \uACBD\uC6B0):
|
|
3041
|
+
\`\`\`bash
|
|
3042
|
+
git add -A
|
|
3043
|
+
git commit -m "docs: complete design phase \u2014 ideation + PRD"
|
|
3044
|
+
\`\`\`
|
|
3045
|
+
|
|
2936
3046
|
## \uC911\uC694 \uC6D0\uCE59
|
|
2937
3047
|
|
|
2938
3048
|
- **\uCD94\uCE21 \uAE08\uC9C0**: \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uBD80\uC871\uD558\uBA74 \uBC18\uB4DC\uC2DC AskUserQuestion\uC73C\uB85C \uC9C8\uBB38
|
|
2939
3049
|
- **\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD30C\uC77C \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uC74C** \u2014 \uBCF4\uAC15\uB9CC \uC81C\uC548
|
|
2940
3050
|
- **\uAC01 Phase \uC644\uB8CC \uC2DC \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACB0\uACFC \uC694\uC57D** \uBCF4\uACE0
|
|
3051
|
+
- **Git \uCEE4\uBC0B\uC740 \uAC01 Phase \uC644\uB8CC \uC9C1\uD6C4** \u2014 \uC124\uACC4 \uC774\uB825 \uCD94\uC801 \uAC00\uB2A5\uD558\uAC8C
|
|
2941
3052
|
`,
|
|
2942
3053
|
"validate.md": `---
|
|
2943
3054
|
name: "PAI: Validate"
|
|
@@ -3604,6 +3715,543 @@ var init_detector = __esm({
|
|
|
3604
3715
|
}
|
|
3605
3716
|
});
|
|
3606
3717
|
|
|
3718
|
+
// src/core/harness-defaults.ts
|
|
3719
|
+
function resolveConfig(partial) {
|
|
3720
|
+
const base = HARNESS_PRESETS[partial.scenario];
|
|
3721
|
+
if (!partial.overrides) return { ...base };
|
|
3722
|
+
const overrides = partial.overrides;
|
|
3723
|
+
return {
|
|
3724
|
+
...base,
|
|
3725
|
+
gates: {
|
|
3726
|
+
preCommit: { ...base.gates.preCommit, ...overrides.gates?.preCommit },
|
|
3727
|
+
prePush: { ...base.gates.prePush, ...overrides.gates?.prePush },
|
|
3728
|
+
ci: { ...base.gates.ci, ...overrides.gates?.ci }
|
|
3729
|
+
},
|
|
3730
|
+
scripts: overrides.scripts ?? base.scripts,
|
|
3731
|
+
permissions: overrides.permissions ? {
|
|
3732
|
+
allow: [...base.permissions.allow, ...overrides.permissions.allow ?? []],
|
|
3733
|
+
deny: [...base.permissions.deny, ...overrides.permissions.deny ?? []]
|
|
3734
|
+
} : base.permissions,
|
|
3735
|
+
rules: { ...base.rules, ...overrides.rules }
|
|
3736
|
+
};
|
|
3737
|
+
}
|
|
3738
|
+
var SCRIPTS_CORE, SCRIPTS_TEST_BASIC, SCRIPTS_TEST_FULL, SCRIPTS_VERIFY, PERMISSIONS_BASE, PERMISSIONS_PRODUCTION, RULES_PROTOTYPE, RULES_PRODUCTION, RULES_TEAM, PRESET_PROTOTYPE, PRESET_PRODUCTION, PRESET_TEAM, HARNESS_PRESETS;
|
|
3739
|
+
var init_harness_defaults = __esm({
|
|
3740
|
+
"src/core/harness-defaults.ts"() {
|
|
3741
|
+
"use strict";
|
|
3742
|
+
SCRIPTS_CORE = [
|
|
3743
|
+
{ name: "format", command: "prettier --write .", required: true },
|
|
3744
|
+
{ name: "lint", command: "eslint .", required: true },
|
|
3745
|
+
{ name: "lint:fix", command: "eslint . --fix", required: false },
|
|
3746
|
+
{ name: "typecheck", command: "tsc --noEmit", required: true },
|
|
3747
|
+
{ name: "build", command: "tsup", required: true }
|
|
3748
|
+
];
|
|
3749
|
+
SCRIPTS_TEST_BASIC = [
|
|
3750
|
+
{ name: "test", command: "vitest run", required: true }
|
|
3751
|
+
];
|
|
3752
|
+
SCRIPTS_TEST_FULL = [
|
|
3753
|
+
{ name: "test", command: "vitest run", required: true },
|
|
3754
|
+
{ name: "test:unit", command: "vitest run --project unit", required: true },
|
|
3755
|
+
{ name: "test:integration", command: "vitest run --project integration", required: false },
|
|
3756
|
+
{ name: "test:smoke", command: "npm run build && node scripts/smoke.js", required: false }
|
|
3757
|
+
];
|
|
3758
|
+
SCRIPTS_VERIFY = [
|
|
3759
|
+
{ name: "verify", command: "npm run lint && npm run typecheck && npm run test", required: true },
|
|
3760
|
+
{ name: "verify:full", command: "npm run lint && npm run typecheck && npm run test && npm run build", required: false }
|
|
3761
|
+
];
|
|
3762
|
+
PERMISSIONS_BASE = {
|
|
3763
|
+
allow: [
|
|
3764
|
+
"Bash(npm run lint)",
|
|
3765
|
+
"Bash(npm run lint:fix)",
|
|
3766
|
+
"Bash(npm run format)",
|
|
3767
|
+
"Bash(npm run typecheck)",
|
|
3768
|
+
"Bash(npm run test)",
|
|
3769
|
+
"Bash(npm run build)",
|
|
3770
|
+
"Read(**)",
|
|
3771
|
+
"Edit(**)"
|
|
3772
|
+
],
|
|
3773
|
+
deny: [
|
|
3774
|
+
"Bash(rm -rf /)",
|
|
3775
|
+
"Bash(git push --force)",
|
|
3776
|
+
"Bash(git reset --hard)",
|
|
3777
|
+
"Bash(curl * | sh)",
|
|
3778
|
+
"Bash(npx -y *)"
|
|
3779
|
+
]
|
|
3780
|
+
};
|
|
3781
|
+
PERMISSIONS_PRODUCTION = {
|
|
3782
|
+
allow: [
|
|
3783
|
+
...PERMISSIONS_BASE.allow,
|
|
3784
|
+
"Bash(npm run test:unit)",
|
|
3785
|
+
"Bash(npm run test:integration)",
|
|
3786
|
+
"Bash(npm run verify)",
|
|
3787
|
+
"Bash(npm run verify:full)"
|
|
3788
|
+
],
|
|
3789
|
+
deny: [
|
|
3790
|
+
...PERMISSIONS_BASE.deny,
|
|
3791
|
+
"Bash(npm publish)",
|
|
3792
|
+
"Bash(git push origin main)"
|
|
3793
|
+
]
|
|
3794
|
+
};
|
|
3795
|
+
RULES_PROTOTYPE = {
|
|
3796
|
+
specBeforeCode: false,
|
|
3797
|
+
prMergeRequiresReview: false,
|
|
3798
|
+
branchProtection: false,
|
|
3799
|
+
requireWorkflow: false
|
|
3800
|
+
};
|
|
3801
|
+
RULES_PRODUCTION = {
|
|
3802
|
+
specBeforeCode: true,
|
|
3803
|
+
prMergeRequiresReview: true,
|
|
3804
|
+
branchProtection: true,
|
|
3805
|
+
requireWorkflow: true
|
|
3806
|
+
};
|
|
3807
|
+
RULES_TEAM = {
|
|
3808
|
+
specBeforeCode: true,
|
|
3809
|
+
prMergeRequiresReview: true,
|
|
3810
|
+
branchProtection: true,
|
|
3811
|
+
requireWorkflow: true
|
|
3812
|
+
};
|
|
3813
|
+
PRESET_PROTOTYPE = {
|
|
3814
|
+
version: "1.0",
|
|
3815
|
+
scenario: "prototype",
|
|
3816
|
+
preset: "prototype",
|
|
3817
|
+
gates: {
|
|
3818
|
+
preCommit: {
|
|
3819
|
+
enabled: true,
|
|
3820
|
+
scripts: ["format", "lint"]
|
|
3821
|
+
},
|
|
3822
|
+
prePush: {
|
|
3823
|
+
enabled: false,
|
|
3824
|
+
scripts: []
|
|
3825
|
+
},
|
|
3826
|
+
ci: {
|
|
3827
|
+
enabled: true,
|
|
3828
|
+
scripts: ["lint", "build"],
|
|
3829
|
+
triggers: {
|
|
3830
|
+
pullRequest: true,
|
|
3831
|
+
push: { branches: ["main"] }
|
|
3832
|
+
},
|
|
3833
|
+
nodeVersion: "20"
|
|
3834
|
+
}
|
|
3835
|
+
},
|
|
3836
|
+
scripts: [...SCRIPTS_CORE, ...SCRIPTS_TEST_BASIC],
|
|
3837
|
+
permissions: PERMISSIONS_BASE,
|
|
3838
|
+
rules: RULES_PROTOTYPE
|
|
3839
|
+
};
|
|
3840
|
+
PRESET_PRODUCTION = {
|
|
3841
|
+
version: "1.0",
|
|
3842
|
+
scenario: "production",
|
|
3843
|
+
preset: "production",
|
|
3844
|
+
gates: {
|
|
3845
|
+
preCommit: {
|
|
3846
|
+
enabled: true,
|
|
3847
|
+
scripts: ["format", "lint"]
|
|
3848
|
+
},
|
|
3849
|
+
prePush: {
|
|
3850
|
+
enabled: true,
|
|
3851
|
+
scripts: ["typecheck", "test:unit"]
|
|
3852
|
+
},
|
|
3853
|
+
ci: {
|
|
3854
|
+
enabled: true,
|
|
3855
|
+
scripts: ["lint", "typecheck", "test", "build"],
|
|
3856
|
+
triggers: {
|
|
3857
|
+
pullRequest: true,
|
|
3858
|
+
push: { branches: ["main", "develop"] }
|
|
3859
|
+
},
|
|
3860
|
+
nodeVersion: "20"
|
|
3861
|
+
}
|
|
3862
|
+
},
|
|
3863
|
+
scripts: [...SCRIPTS_CORE, ...SCRIPTS_TEST_FULL, ...SCRIPTS_VERIFY],
|
|
3864
|
+
permissions: PERMISSIONS_PRODUCTION,
|
|
3865
|
+
rules: RULES_PRODUCTION
|
|
3866
|
+
};
|
|
3867
|
+
PRESET_TEAM = {
|
|
3868
|
+
version: "1.0",
|
|
3869
|
+
scenario: "team",
|
|
3870
|
+
preset: "team",
|
|
3871
|
+
gates: {
|
|
3872
|
+
preCommit: {
|
|
3873
|
+
enabled: true,
|
|
3874
|
+
scripts: ["format", "lint"]
|
|
3875
|
+
},
|
|
3876
|
+
prePush: {
|
|
3877
|
+
enabled: true,
|
|
3878
|
+
scripts: ["typecheck", "test:unit"]
|
|
3879
|
+
},
|
|
3880
|
+
ci: {
|
|
3881
|
+
enabled: true,
|
|
3882
|
+
scripts: ["lint", "typecheck", "test", "build"],
|
|
3883
|
+
triggers: {
|
|
3884
|
+
pullRequest: true,
|
|
3885
|
+
push: { branches: ["main", "develop"] }
|
|
3886
|
+
},
|
|
3887
|
+
nodeVersion: "20"
|
|
3888
|
+
}
|
|
3889
|
+
},
|
|
3890
|
+
scripts: [...SCRIPTS_CORE, ...SCRIPTS_TEST_FULL, ...SCRIPTS_VERIFY],
|
|
3891
|
+
permissions: PERMISSIONS_PRODUCTION,
|
|
3892
|
+
rules: RULES_TEAM
|
|
3893
|
+
};
|
|
3894
|
+
HARNESS_PRESETS = {
|
|
3895
|
+
prototype: PRESET_PROTOTYPE,
|
|
3896
|
+
production: PRESET_PRODUCTION,
|
|
3897
|
+
team: PRESET_TEAM
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
});
|
|
3901
|
+
|
|
3902
|
+
// src/roboco/generators/harness-json.ts
|
|
3903
|
+
import { writeFile } from "fs/promises";
|
|
3904
|
+
import { existsSync } from "fs";
|
|
3905
|
+
import { join as join6 } from "path";
|
|
3906
|
+
async function generateHarnessJson(opts) {
|
|
3907
|
+
const target = join6(opts.cwd, FILENAME);
|
|
3908
|
+
if (existsSync(target) && !opts.force) {
|
|
3909
|
+
return [{ file: FILENAME, action: "skipped", reason: "already exists" }];
|
|
3910
|
+
}
|
|
3911
|
+
const content = JSON.stringify(opts.config, null, 2) + "\n";
|
|
3912
|
+
await writeFile(target, content, "utf8");
|
|
3913
|
+
return [{ file: FILENAME, action: existsSync(target) ? "updated" : "created" }];
|
|
3914
|
+
}
|
|
3915
|
+
var FILENAME;
|
|
3916
|
+
var init_harness_json = __esm({
|
|
3917
|
+
"src/roboco/generators/harness-json.ts"() {
|
|
3918
|
+
"use strict";
|
|
3919
|
+
FILENAME = ".harness.json";
|
|
3920
|
+
}
|
|
3921
|
+
});
|
|
3922
|
+
|
|
3923
|
+
// src/roboco/generators/scripts.ts
|
|
3924
|
+
import { readFile, writeFile as writeFile2 } from "fs/promises";
|
|
3925
|
+
import { existsSync as existsSync2 } from "fs";
|
|
3926
|
+
import { join as join7 } from "path";
|
|
3927
|
+
async function generateScripts(opts) {
|
|
3928
|
+
const pkgPath = join7(opts.cwd, "package.json");
|
|
3929
|
+
if (!existsSync2(pkgPath)) {
|
|
3930
|
+
return [{ file: "package.json", action: "skipped", reason: "not found" }];
|
|
3931
|
+
}
|
|
3932
|
+
const raw = await readFile(pkgPath, "utf8");
|
|
3933
|
+
const pkg5 = JSON.parse(raw);
|
|
3934
|
+
const existing = pkg5.scripts ?? {};
|
|
3935
|
+
const added = [];
|
|
3936
|
+
for (const req of opts.config.scripts) {
|
|
3937
|
+
if (existing[req.name]) continue;
|
|
3938
|
+
if (!req.required && !opts.force) continue;
|
|
3939
|
+
existing[req.name] = req.command;
|
|
3940
|
+
added.push(req.name);
|
|
3941
|
+
}
|
|
3942
|
+
if (added.length === 0) {
|
|
3943
|
+
return [{ file: "package.json", action: "skipped", reason: "all scripts present" }];
|
|
3944
|
+
}
|
|
3945
|
+
pkg5.scripts = existing;
|
|
3946
|
+
const content = JSON.stringify(pkg5, null, 2) + "\n";
|
|
3947
|
+
await writeFile2(pkgPath, content, "utf8");
|
|
3948
|
+
return [{ file: "package.json", action: "updated", reason: `added: ${added.join(", ")}` }];
|
|
3949
|
+
}
|
|
3950
|
+
var init_scripts = __esm({
|
|
3951
|
+
"src/roboco/generators/scripts.ts"() {
|
|
3952
|
+
"use strict";
|
|
3953
|
+
}
|
|
3954
|
+
});
|
|
3955
|
+
|
|
3956
|
+
// src/roboco/generators/lint-staged.ts
|
|
3957
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
3958
|
+
import { existsSync as existsSync3 } from "fs";
|
|
3959
|
+
import { join as join8 } from "path";
|
|
3960
|
+
async function generateLintStaged(opts) {
|
|
3961
|
+
const target = join8(opts.cwd, FILENAME2);
|
|
3962
|
+
if (existsSync3(target) && !opts.force) {
|
|
3963
|
+
return [{ file: FILENAME2, action: "skipped", reason: "already exists" }];
|
|
3964
|
+
}
|
|
3965
|
+
const scripts = opts.config.gates.preCommit.scripts;
|
|
3966
|
+
const hasFormat = scripts.includes("format");
|
|
3967
|
+
const hasLint = scripts.includes("lint");
|
|
3968
|
+
const config = {};
|
|
3969
|
+
if (hasFormat) {
|
|
3970
|
+
config["*.{js,ts,tsx,json,md}"] = ["prettier --write"];
|
|
3971
|
+
}
|
|
3972
|
+
if (hasLint) {
|
|
3973
|
+
config["*.{js,ts,tsx}"] = [
|
|
3974
|
+
...config["*.{js,ts,tsx}"] ?? [],
|
|
3975
|
+
"eslint --fix"
|
|
3976
|
+
];
|
|
3977
|
+
}
|
|
3978
|
+
if (Object.keys(config).length === 0) {
|
|
3979
|
+
return [{ file: FILENAME2, action: "skipped", reason: "no preCommit scripts mapped" }];
|
|
3980
|
+
}
|
|
3981
|
+
const content = JSON.stringify(config, null, 2) + "\n";
|
|
3982
|
+
await writeFile3(target, content, "utf8");
|
|
3983
|
+
return [{ file: FILENAME2, action: "created" }];
|
|
3984
|
+
}
|
|
3985
|
+
var FILENAME2;
|
|
3986
|
+
var init_lint_staged = __esm({
|
|
3987
|
+
"src/roboco/generators/lint-staged.ts"() {
|
|
3988
|
+
"use strict";
|
|
3989
|
+
FILENAME2 = ".lintstagedrc.json";
|
|
3990
|
+
}
|
|
3991
|
+
});
|
|
3992
|
+
|
|
3993
|
+
// src/roboco/generators/husky.ts
|
|
3994
|
+
import { writeFile as writeFile4, mkdir } from "fs/promises";
|
|
3995
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3996
|
+
import { join as join9 } from "path";
|
|
3997
|
+
function buildHookScript(scripts, useLintStaged) {
|
|
3998
|
+
const lines = ["#!/usr/bin/env sh", '. "$(dirname -- "$0")/_/husky.sh"', ""];
|
|
3999
|
+
if (useLintStaged) {
|
|
4000
|
+
lines.push("npx lint-staged");
|
|
4001
|
+
} else {
|
|
4002
|
+
for (const s of scripts) {
|
|
4003
|
+
lines.push(`npm run ${s}`);
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
return lines.join("\n") + "\n";
|
|
4007
|
+
}
|
|
4008
|
+
async function generateHusky(opts) {
|
|
4009
|
+
const results = [];
|
|
4010
|
+
const huskyDir = join9(opts.cwd, HUSKY_DIR);
|
|
4011
|
+
await mkdir(huskyDir, { recursive: true });
|
|
4012
|
+
const preCommit = opts.config.gates.preCommit;
|
|
4013
|
+
if (preCommit.enabled) {
|
|
4014
|
+
const file = join9(huskyDir, "pre-commit");
|
|
4015
|
+
const relPath = `${HUSKY_DIR}/pre-commit`;
|
|
4016
|
+
if (existsSync4(file) && !opts.force) {
|
|
4017
|
+
results.push({ file: relPath, action: "skipped", reason: "already exists" });
|
|
4018
|
+
} else {
|
|
4019
|
+
const content = buildHookScript(preCommit.scripts, true);
|
|
4020
|
+
await writeFile4(file, content, { mode: 493 });
|
|
4021
|
+
results.push({ file: relPath, action: "created" });
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
const prePush = opts.config.gates.prePush;
|
|
4025
|
+
if (prePush.enabled) {
|
|
4026
|
+
const file = join9(huskyDir, "pre-push");
|
|
4027
|
+
const relPath = `${HUSKY_DIR}/pre-push`;
|
|
4028
|
+
if (existsSync4(file) && !opts.force) {
|
|
4029
|
+
results.push({ file: relPath, action: "skipped", reason: "already exists" });
|
|
4030
|
+
} else {
|
|
4031
|
+
const content = buildHookScript(prePush.scripts, false);
|
|
4032
|
+
await writeFile4(file, content, { mode: 493 });
|
|
4033
|
+
results.push({ file: relPath, action: "created" });
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
return results;
|
|
4037
|
+
}
|
|
4038
|
+
var HUSKY_DIR;
|
|
4039
|
+
var init_husky = __esm({
|
|
4040
|
+
"src/roboco/generators/husky.ts"() {
|
|
4041
|
+
"use strict";
|
|
4042
|
+
HUSKY_DIR = ".husky";
|
|
4043
|
+
}
|
|
4044
|
+
});
|
|
4045
|
+
|
|
4046
|
+
// src/roboco/generators/ci-workflow.ts
|
|
4047
|
+
import { writeFile as writeFile5, mkdir as mkdir2 } from "fs/promises";
|
|
4048
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4049
|
+
import { join as join10 } from "path";
|
|
4050
|
+
function buildWorkflow(opts) {
|
|
4051
|
+
const ci = opts.config.gates.ci;
|
|
4052
|
+
const triggers = [];
|
|
4053
|
+
if (ci.triggers.pullRequest) {
|
|
4054
|
+
triggers.push(" pull_request:");
|
|
4055
|
+
}
|
|
4056
|
+
if (ci.triggers.push.branches.length > 0) {
|
|
4057
|
+
triggers.push(" push:");
|
|
4058
|
+
triggers.push(` branches: [${ci.triggers.push.branches.join(", ")}]`);
|
|
4059
|
+
}
|
|
4060
|
+
const steps = [
|
|
4061
|
+
" - uses: actions/checkout@v4",
|
|
4062
|
+
" - uses: actions/setup-node@v4",
|
|
4063
|
+
" with:",
|
|
4064
|
+
` node-version: ${ci.nodeVersion}`,
|
|
4065
|
+
" - run: npm ci"
|
|
4066
|
+
];
|
|
4067
|
+
for (const script of ci.scripts) {
|
|
4068
|
+
steps.push(` - run: npm run ${script}`);
|
|
4069
|
+
}
|
|
4070
|
+
return [
|
|
4071
|
+
"name: CI",
|
|
4072
|
+
"",
|
|
4073
|
+
"on:",
|
|
4074
|
+
...triggers,
|
|
4075
|
+
"",
|
|
4076
|
+
"jobs:",
|
|
4077
|
+
" verify:",
|
|
4078
|
+
" runs-on: ubuntu-latest",
|
|
4079
|
+
" steps:",
|
|
4080
|
+
...steps,
|
|
4081
|
+
""
|
|
4082
|
+
].join("\n");
|
|
4083
|
+
}
|
|
4084
|
+
async function generateCIWorkflow(opts) {
|
|
4085
|
+
if (!opts.config.gates.ci.enabled) {
|
|
4086
|
+
return [{ file: REL_PATH, action: "skipped", reason: "CI gate disabled" }];
|
|
4087
|
+
}
|
|
4088
|
+
const target = join10(opts.cwd, REL_PATH);
|
|
4089
|
+
if (existsSync5(target) && !opts.force) {
|
|
4090
|
+
return [{ file: REL_PATH, action: "skipped", reason: "already exists" }];
|
|
4091
|
+
}
|
|
4092
|
+
await mkdir2(join10(opts.cwd, ".github", "workflows"), { recursive: true });
|
|
4093
|
+
const content = buildWorkflow(opts);
|
|
4094
|
+
await writeFile5(target, content, "utf8");
|
|
4095
|
+
return [{ file: REL_PATH, action: "created" }];
|
|
4096
|
+
}
|
|
4097
|
+
var REL_PATH;
|
|
4098
|
+
var init_ci_workflow = __esm({
|
|
4099
|
+
"src/roboco/generators/ci-workflow.ts"() {
|
|
4100
|
+
"use strict";
|
|
4101
|
+
REL_PATH = ".github/workflows/ci.yml";
|
|
4102
|
+
}
|
|
4103
|
+
});
|
|
4104
|
+
|
|
4105
|
+
// src/roboco/generators/claude-settings.ts
|
|
4106
|
+
import { readFile as readFile2, writeFile as writeFile6, mkdir as mkdir3 } from "fs/promises";
|
|
4107
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4108
|
+
import { join as join11 } from "path";
|
|
4109
|
+
function dedup(arr) {
|
|
4110
|
+
return [...new Set(arr)];
|
|
4111
|
+
}
|
|
4112
|
+
async function generateClaudeSettings2(opts) {
|
|
4113
|
+
const target = join11(opts.cwd, REL_PATH2);
|
|
4114
|
+
await mkdir3(join11(opts.cwd, ".claude"), { recursive: true });
|
|
4115
|
+
let settings = {};
|
|
4116
|
+
if (existsSync6(target)) {
|
|
4117
|
+
try {
|
|
4118
|
+
const raw = await readFile2(target, "utf8");
|
|
4119
|
+
settings = JSON.parse(raw.replace(/^\uFEFF/, ""));
|
|
4120
|
+
} catch {
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
const existing = settings.permissions ?? {};
|
|
4124
|
+
const merged = {
|
|
4125
|
+
allow: dedup([...existing.allow ?? [], ...opts.config.permissions.allow]),
|
|
4126
|
+
deny: dedup([...existing.deny ?? [], ...opts.config.permissions.deny])
|
|
4127
|
+
};
|
|
4128
|
+
settings.permissions = merged;
|
|
4129
|
+
const content = JSON.stringify(settings, null, 2) + "\n";
|
|
4130
|
+
await writeFile6(target, content, "utf8");
|
|
4131
|
+
return [{ file: REL_PATH2, action: existsSync6(target) ? "updated" : "created" }];
|
|
4132
|
+
}
|
|
4133
|
+
var REL_PATH2;
|
|
4134
|
+
var init_claude_settings = __esm({
|
|
4135
|
+
"src/roboco/generators/claude-settings.ts"() {
|
|
4136
|
+
"use strict";
|
|
4137
|
+
REL_PATH2 = ".claude/settings.json";
|
|
4138
|
+
}
|
|
4139
|
+
});
|
|
4140
|
+
|
|
4141
|
+
// src/roboco/generators/claude-md.ts
|
|
4142
|
+
import { readFile as readFile3, writeFile as writeFile7 } from "fs/promises";
|
|
4143
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4144
|
+
import { join as join12 } from "path";
|
|
4145
|
+
function buildRulesBlock(opts) {
|
|
4146
|
+
const { rules, scenario } = opts.config;
|
|
4147
|
+
const lines = [
|
|
4148
|
+
MARKER_START,
|
|
4149
|
+
`## Harness Rules (${scenario})`,
|
|
4150
|
+
""
|
|
4151
|
+
];
|
|
4152
|
+
if (rules.specBeforeCode) {
|
|
4153
|
+
lines.push("- spec \uC5C6\uB294 \uAE30\uB2A5 \uAD6C\uD604 \uAE08\uC9C0 \u2014 \uBC18\uB4DC\uC2DC OpenSpec \uBA3C\uC800 \uC791\uC131");
|
|
4154
|
+
}
|
|
4155
|
+
if (rules.requireWorkflow) {
|
|
4156
|
+
lines.push("- \uD070 \uBCC0\uACBD\uC740 plan \u2192 implement \u2192 review \u2192 verify \uC21C\uC11C\uB85C \uC9C4\uD589");
|
|
4157
|
+
}
|
|
4158
|
+
if (rules.prMergeRequiresReview) {
|
|
4159
|
+
lines.push("- PR \uBA38\uC9C0 \uC804 \uB9AC\uBDF0 \uD544\uC218");
|
|
4160
|
+
}
|
|
4161
|
+
if (rules.branchProtection) {
|
|
4162
|
+
lines.push("- main/develop \uBE0C\uB79C\uCE58 \uC9C1\uC811 push \uAE08\uC9C0");
|
|
4163
|
+
}
|
|
4164
|
+
lines.push("- pre-commit/pre-push/CI\uB97C \uD1B5\uACFC\uD558\uC9C0 \uBABB\uD558\uBA74 \uC644\uB8CC\uB85C \uBCF4\uC9C0 \uC54A\uC74C");
|
|
4165
|
+
lines.push("- \uAE30\uC874 script \uC678 \uC784\uC758 \uBA85\uB839 \uCD5C\uC18C\uD654 \u2014 package.json scripts\uB97C \uC0AC\uC6A9");
|
|
4166
|
+
lines.push("");
|
|
4167
|
+
lines.push(MARKER_END);
|
|
4168
|
+
return lines.join("\n");
|
|
4169
|
+
}
|
|
4170
|
+
async function generateClaudeMd2(opts) {
|
|
4171
|
+
const target = join12(opts.cwd, FILENAME3);
|
|
4172
|
+
const block = buildRulesBlock(opts);
|
|
4173
|
+
if (!existsSync7(target)) {
|
|
4174
|
+
const content2 = `# Project Rules
|
|
4175
|
+
|
|
4176
|
+
${block}
|
|
4177
|
+
`;
|
|
4178
|
+
await writeFile7(target, content2, "utf8");
|
|
4179
|
+
return [{ file: FILENAME3, action: "created" }];
|
|
4180
|
+
}
|
|
4181
|
+
let content = await readFile3(target, "utf8");
|
|
4182
|
+
const startIdx = content.indexOf(MARKER_START);
|
|
4183
|
+
const endIdx = content.indexOf(MARKER_END);
|
|
4184
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
4185
|
+
content = content.slice(0, startIdx) + block + content.slice(endIdx + MARKER_END.length);
|
|
4186
|
+
} else {
|
|
4187
|
+
content = content.trimEnd() + "\n\n" + block + "\n";
|
|
4188
|
+
}
|
|
4189
|
+
await writeFile7(target, content, "utf8");
|
|
4190
|
+
return [{ file: FILENAME3, action: "updated" }];
|
|
4191
|
+
}
|
|
4192
|
+
var FILENAME3, MARKER_START, MARKER_END;
|
|
4193
|
+
var init_claude_md = __esm({
|
|
4194
|
+
"src/roboco/generators/claude-md.ts"() {
|
|
4195
|
+
"use strict";
|
|
4196
|
+
FILENAME3 = "CLAUDE.md";
|
|
4197
|
+
MARKER_START = "<!-- harness:start -->";
|
|
4198
|
+
MARKER_END = "<!-- harness:end -->";
|
|
4199
|
+
}
|
|
4200
|
+
});
|
|
4201
|
+
|
|
4202
|
+
// src/roboco/generators/index.ts
|
|
4203
|
+
var init_generators = __esm({
|
|
4204
|
+
"src/roboco/generators/index.ts"() {
|
|
4205
|
+
"use strict";
|
|
4206
|
+
init_harness_json();
|
|
4207
|
+
init_scripts();
|
|
4208
|
+
init_lint_staged();
|
|
4209
|
+
init_husky();
|
|
4210
|
+
init_ci_workflow();
|
|
4211
|
+
init_claude_settings();
|
|
4212
|
+
init_claude_md();
|
|
4213
|
+
}
|
|
4214
|
+
});
|
|
4215
|
+
|
|
4216
|
+
// src/roboco/scaffold.ts
|
|
4217
|
+
async function scaffold(opts) {
|
|
4218
|
+
const scenario = opts.scenario ?? "prototype";
|
|
4219
|
+
const config = resolveConfig({ scenario, overrides: opts.overrides });
|
|
4220
|
+
const generatorOpts = {
|
|
4221
|
+
cwd: opts.cwd,
|
|
4222
|
+
config,
|
|
4223
|
+
force: opts.force
|
|
4224
|
+
};
|
|
4225
|
+
const files = [];
|
|
4226
|
+
for (const gen of GENERATORS) {
|
|
4227
|
+
const results = await gen(generatorOpts);
|
|
4228
|
+
files.push(...results);
|
|
4229
|
+
}
|
|
4230
|
+
const summary = {
|
|
4231
|
+
created: files.filter((f) => f.action === "created").length,
|
|
4232
|
+
updated: files.filter((f) => f.action === "updated").length,
|
|
4233
|
+
skipped: files.filter((f) => f.action === "skipped").length
|
|
4234
|
+
};
|
|
4235
|
+
return { scenario, config, files, summary };
|
|
4236
|
+
}
|
|
4237
|
+
var GENERATORS;
|
|
4238
|
+
var init_scaffold = __esm({
|
|
4239
|
+
"src/roboco/scaffold.ts"() {
|
|
4240
|
+
"use strict";
|
|
4241
|
+
init_harness_defaults();
|
|
4242
|
+
init_generators();
|
|
4243
|
+
GENERATORS = [
|
|
4244
|
+
generateHarnessJson,
|
|
4245
|
+
generateScripts,
|
|
4246
|
+
generateLintStaged,
|
|
4247
|
+
generateHusky,
|
|
4248
|
+
generateCIWorkflow,
|
|
4249
|
+
generateClaudeSettings2,
|
|
4250
|
+
generateClaudeMd2
|
|
4251
|
+
];
|
|
4252
|
+
}
|
|
4253
|
+
});
|
|
4254
|
+
|
|
3607
4255
|
// src/core/roboco.ts
|
|
3608
4256
|
import os2 from "os";
|
|
3609
4257
|
import path5 from "path";
|
|
@@ -4143,7 +4791,7 @@ var doctor_exports = {};
|
|
|
4143
4791
|
__export(doctor_exports, {
|
|
4144
4792
|
runDoctor: () => runDoctor
|
|
4145
4793
|
});
|
|
4146
|
-
import { join as
|
|
4794
|
+
import { join as join13 } from "path";
|
|
4147
4795
|
import { homedir } from "os";
|
|
4148
4796
|
import fs12 from "fs-extra";
|
|
4149
4797
|
async function runDoctor() {
|
|
@@ -4164,7 +4812,7 @@ async function runDoctor() {
|
|
|
4164
4812
|
detail: claudeCheck.detail,
|
|
4165
4813
|
fix: claudeCheck.ok ? void 0 : "npm install -g @anthropic-ai/claude-code"
|
|
4166
4814
|
});
|
|
4167
|
-
const globalConfigPath =
|
|
4815
|
+
const globalConfigPath = join13(homedir(), ".pai", "config.json");
|
|
4168
4816
|
const hasGlobalConfig = await fs12.pathExists(globalConfigPath);
|
|
4169
4817
|
checks.push({
|
|
4170
4818
|
label: "\uAE00\uB85C\uBC8C \uC124\uC815",
|
|
@@ -4542,6 +5190,16 @@ var init_fetch_cmd = __esm({
|
|
|
4542
5190
|
});
|
|
4543
5191
|
|
|
4544
5192
|
// src/stages/environment/index.ts
|
|
5193
|
+
function modeToScenario(mode) {
|
|
5194
|
+
switch (mode) {
|
|
5195
|
+
case "prototype":
|
|
5196
|
+
return "prototype";
|
|
5197
|
+
case "poc":
|
|
5198
|
+
return "production";
|
|
5199
|
+
case "production":
|
|
5200
|
+
return "production";
|
|
5201
|
+
}
|
|
5202
|
+
}
|
|
4545
5203
|
var environmentStage;
|
|
4546
5204
|
var init_environment = __esm({
|
|
4547
5205
|
"src/stages/environment/index.ts"() {
|
|
@@ -4554,6 +5212,7 @@ var init_environment = __esm({
|
|
|
4554
5212
|
init_claude_commands();
|
|
4555
5213
|
init_detector();
|
|
4556
5214
|
init_config();
|
|
5215
|
+
init_scaffold();
|
|
4557
5216
|
init_progress();
|
|
4558
5217
|
init_ui();
|
|
4559
5218
|
init_analyzer();
|
|
@@ -4672,6 +5331,21 @@ var init_environment = __esm({
|
|
|
4672
5331
|
}
|
|
4673
5332
|
}
|
|
4674
5333
|
console.log("");
|
|
5334
|
+
const scaffoldResult = await withSpinner("Harness \uC815\uCC45 \uC124\uC815 \uC911...", async () => {
|
|
5335
|
+
const result = await scaffold({
|
|
5336
|
+
cwd: input.cwd,
|
|
5337
|
+
scenario: modeToScenario(interview.mode)
|
|
5338
|
+
});
|
|
5339
|
+
await sleep(300);
|
|
5340
|
+
return result;
|
|
5341
|
+
});
|
|
5342
|
+
if (scaffoldResult.summary.created > 0 || scaffoldResult.summary.updated > 0) {
|
|
5343
|
+
const parts = [];
|
|
5344
|
+
if (scaffoldResult.summary.created > 0) parts.push(`${scaffoldResult.summary.created}\uAC1C \uC0DD\uC131`);
|
|
5345
|
+
if (scaffoldResult.summary.updated > 0) parts.push(`${scaffoldResult.summary.updated}\uAC1C \uC218\uC815`);
|
|
5346
|
+
success(`Harness: ${parts.join(", ")}`);
|
|
5347
|
+
}
|
|
5348
|
+
console.log("");
|
|
4675
5349
|
await withSpinner("\uC124\uC815 \uC800\uC7A5 \uC911...", async () => {
|
|
4676
5350
|
const config = createDefaultConfig(interview.projectName, interview.mode);
|
|
4677
5351
|
config.plugins = pluginKeys;
|
|
@@ -4706,11 +5380,11 @@ var init_environment = __esm({
|
|
|
4706
5380
|
});
|
|
4707
5381
|
|
|
4708
5382
|
// src/stages/validation/runner.ts
|
|
4709
|
-
import { join as
|
|
5383
|
+
import { join as join14 } from "path";
|
|
4710
5384
|
import fs15 from "fs-extra";
|
|
4711
5385
|
async function runTests(cwd) {
|
|
4712
5386
|
const start = Date.now();
|
|
4713
|
-
const gstackPath =
|
|
5387
|
+
const gstackPath = join14(cwd, ".pai", "gstack.json");
|
|
4714
5388
|
let runner = "npm test";
|
|
4715
5389
|
if (await fs15.pathExists(gstackPath)) {
|
|
4716
5390
|
try {
|
|
@@ -4721,7 +5395,7 @@ async function runTests(cwd) {
|
|
|
4721
5395
|
} catch {
|
|
4722
5396
|
}
|
|
4723
5397
|
}
|
|
4724
|
-
const pkgPath =
|
|
5398
|
+
const pkgPath = join14(cwd, "package.json");
|
|
4725
5399
|
if (await fs15.pathExists(pkgPath)) {
|
|
4726
5400
|
try {
|
|
4727
5401
|
const pkg5 = await fs15.readJson(pkgPath);
|
|
@@ -4766,10 +5440,10 @@ var init_runner = __esm({
|
|
|
4766
5440
|
});
|
|
4767
5441
|
|
|
4768
5442
|
// src/stages/validation/harness.ts
|
|
4769
|
-
import { join as
|
|
5443
|
+
import { join as join15 } from "path";
|
|
4770
5444
|
import fs16 from "fs-extra";
|
|
4771
5445
|
async function runHarnessCheck(cwd) {
|
|
4772
|
-
const harnessPath =
|
|
5446
|
+
const harnessPath = join15(cwd, ".pai", "harness.json");
|
|
4773
5447
|
if (!await fs16.pathExists(harnessPath)) {
|
|
4774
5448
|
return { enabled: false, specFile: null, rules: [], checks: [] };
|
|
4775
5449
|
}
|
|
@@ -4783,8 +5457,8 @@ async function runHarnessCheck(cwd) {
|
|
|
4783
5457
|
const rules = config.rules ?? [];
|
|
4784
5458
|
const checks = [];
|
|
4785
5459
|
if (rules.includes("spec-implementation-match")) {
|
|
4786
|
-
const specExists = await fs16.pathExists(
|
|
4787
|
-
const srcExists = await fs16.pathExists(
|
|
5460
|
+
const specExists = await fs16.pathExists(join15(cwd, specFile));
|
|
5461
|
+
const srcExists = await fs16.pathExists(join15(cwd, "src"));
|
|
4788
5462
|
checks.push({
|
|
4789
5463
|
rule: "spec-implementation-match",
|
|
4790
5464
|
passed: specExists && srcExists,
|
|
@@ -4792,8 +5466,8 @@ async function runHarnessCheck(cwd) {
|
|
|
4792
5466
|
});
|
|
4793
5467
|
}
|
|
4794
5468
|
if (rules.includes("api-contract-test")) {
|
|
4795
|
-
const testDir = await fs16.pathExists(
|
|
4796
|
-
const testDir2 = await fs16.pathExists(
|
|
5469
|
+
const testDir = await fs16.pathExists(join15(cwd, "tests"));
|
|
5470
|
+
const testDir2 = await fs16.pathExists(join15(cwd, "test"));
|
|
4797
5471
|
checks.push({
|
|
4798
5472
|
rule: "api-contract-test",
|
|
4799
5473
|
passed: testDir || testDir2,
|
|
@@ -4950,12 +5624,189 @@ var init_analyze = __esm({
|
|
|
4950
5624
|
}
|
|
4951
5625
|
});
|
|
4952
5626
|
|
|
5627
|
+
// src/vibe-ready/evaluator.ts
|
|
5628
|
+
var evaluator_exports = {};
|
|
5629
|
+
__export(evaluator_exports, {
|
|
5630
|
+
evaluateHarness: () => evaluateHarness
|
|
5631
|
+
});
|
|
5632
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
5633
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5634
|
+
import { join as join16 } from "path";
|
|
5635
|
+
async function evaluateHarness(cwd) {
|
|
5636
|
+
const configPath = join16(cwd, ".harness.json");
|
|
5637
|
+
if (!existsSync8(configPath)) {
|
|
5638
|
+
return {
|
|
5639
|
+
configFound: false,
|
|
5640
|
+
scenario: null,
|
|
5641
|
+
gates: [],
|
|
5642
|
+
missingScripts: [],
|
|
5643
|
+
missingFiles: [],
|
|
5644
|
+
score: 0
|
|
5645
|
+
};
|
|
5646
|
+
}
|
|
5647
|
+
let config;
|
|
5648
|
+
try {
|
|
5649
|
+
const raw = await readFile4(configPath, "utf8");
|
|
5650
|
+
config = JSON.parse(raw);
|
|
5651
|
+
} catch {
|
|
5652
|
+
return {
|
|
5653
|
+
configFound: false,
|
|
5654
|
+
scenario: null,
|
|
5655
|
+
gates: [],
|
|
5656
|
+
missingScripts: [],
|
|
5657
|
+
missingFiles: [],
|
|
5658
|
+
score: 0
|
|
5659
|
+
};
|
|
5660
|
+
}
|
|
5661
|
+
const gates = [];
|
|
5662
|
+
const missingScripts = [];
|
|
5663
|
+
const missingFiles = [];
|
|
5664
|
+
let score = 10;
|
|
5665
|
+
gates.push(await evaluateGate(cwd, config, "preCommit", [".husky/pre-commit", ".lintstagedrc.json"]));
|
|
5666
|
+
if (gates[gates.length - 1].status === "pass") score += 15;
|
|
5667
|
+
else missingFiles.push(...gates[gates.length - 1].findings.filter((f) => f.startsWith("missing:")).map((f) => f.slice(8)));
|
|
5668
|
+
gates.push(await evaluateGate(cwd, config, "prePush", [".husky/pre-push"]));
|
|
5669
|
+
if (gates[gates.length - 1].status === "pass") score += 15;
|
|
5670
|
+
else if (gates[gates.length - 1].status === "skip") score += 15;
|
|
5671
|
+
gates.push(await evaluateGate(cwd, config, "ci", [".github/workflows/ci.yml"]));
|
|
5672
|
+
if (gates[gates.length - 1].status === "pass") score += 20;
|
|
5673
|
+
const scriptScore = await evaluateScripts(cwd, config, missingScripts);
|
|
5674
|
+
score += scriptScore;
|
|
5675
|
+
const permScore = await evaluatePermissions(cwd, config);
|
|
5676
|
+
score += permScore;
|
|
5677
|
+
const rulesScore = await evaluateRules(cwd, config);
|
|
5678
|
+
score += rulesScore;
|
|
5679
|
+
return {
|
|
5680
|
+
configFound: true,
|
|
5681
|
+
scenario: config.scenario,
|
|
5682
|
+
gates,
|
|
5683
|
+
missingScripts,
|
|
5684
|
+
missingFiles,
|
|
5685
|
+
score: Math.min(100, score)
|
|
5686
|
+
};
|
|
5687
|
+
}
|
|
5688
|
+
async function evaluateGate(cwd, config, gateName, expectedFiles) {
|
|
5689
|
+
const gate = config.gates[gateName];
|
|
5690
|
+
if (!gate.enabled) {
|
|
5691
|
+
return { gate: gateName, status: "skip", findings: ["gate disabled"] };
|
|
5692
|
+
}
|
|
5693
|
+
const findings = [];
|
|
5694
|
+
let allPresent = true;
|
|
5695
|
+
for (const file of expectedFiles) {
|
|
5696
|
+
if (!existsSync8(join16(cwd, file))) {
|
|
5697
|
+
findings.push(`missing:${file}`);
|
|
5698
|
+
allPresent = false;
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
const pkg5 = await readPackageJson(cwd);
|
|
5702
|
+
if (pkg5) {
|
|
5703
|
+
for (const script of gate.scripts) {
|
|
5704
|
+
if (!pkg5.scripts?.[script]) {
|
|
5705
|
+
findings.push(`missing script: ${script}`);
|
|
5706
|
+
allPresent = false;
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5709
|
+
}
|
|
5710
|
+
return {
|
|
5711
|
+
gate: gateName,
|
|
5712
|
+
status: allPresent ? "pass" : "fail",
|
|
5713
|
+
findings
|
|
5714
|
+
};
|
|
5715
|
+
}
|
|
5716
|
+
async function evaluateScripts(cwd, config, missingScripts) {
|
|
5717
|
+
const pkg5 = await readPackageJson(cwd);
|
|
5718
|
+
if (!pkg5) return 0;
|
|
5719
|
+
const required = config.scripts.filter((s) => s.required);
|
|
5720
|
+
if (required.length === 0) return 20;
|
|
5721
|
+
let found = 0;
|
|
5722
|
+
for (const req of required) {
|
|
5723
|
+
if (pkg5.scripts?.[req.name]) {
|
|
5724
|
+
found++;
|
|
5725
|
+
} else {
|
|
5726
|
+
missingScripts.push(req.name);
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5729
|
+
return Math.round(found / required.length * 20);
|
|
5730
|
+
}
|
|
5731
|
+
async function evaluatePermissions(cwd, config) {
|
|
5732
|
+
const settingsPath = join16(cwd, ".claude", "settings.json");
|
|
5733
|
+
if (!existsSync8(settingsPath)) return 0;
|
|
5734
|
+
try {
|
|
5735
|
+
const raw = await readFile4(settingsPath, "utf8");
|
|
5736
|
+
const settings = JSON.parse(raw.replace(/^\uFEFF/, ""));
|
|
5737
|
+
const perms = settings.permissions;
|
|
5738
|
+
if (!perms) return 0;
|
|
5739
|
+
let matchCount = 0;
|
|
5740
|
+
let totalChecks = 0;
|
|
5741
|
+
for (const deny of config.permissions.deny) {
|
|
5742
|
+
totalChecks++;
|
|
5743
|
+
if (perms.deny?.includes(deny)) matchCount++;
|
|
5744
|
+
}
|
|
5745
|
+
for (const allow of config.permissions.allow) {
|
|
5746
|
+
totalChecks++;
|
|
5747
|
+
if (perms.allow?.includes(allow)) matchCount++;
|
|
5748
|
+
}
|
|
5749
|
+
if (totalChecks === 0) return 10;
|
|
5750
|
+
return Math.round(matchCount / totalChecks * 10);
|
|
5751
|
+
} catch {
|
|
5752
|
+
return 0;
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
async function evaluateRules(cwd, config) {
|
|
5756
|
+
const claudeMdPath = join16(cwd, "CLAUDE.md");
|
|
5757
|
+
if (!existsSync8(claudeMdPath)) return 0;
|
|
5758
|
+
try {
|
|
5759
|
+
const content = await readFile4(claudeMdPath, "utf8");
|
|
5760
|
+
let matchCount = 0;
|
|
5761
|
+
let totalRules = 0;
|
|
5762
|
+
if (config.rules.specBeforeCode) {
|
|
5763
|
+
totalRules++;
|
|
5764
|
+
if (content.includes("spec") && content.includes("\uAE08\uC9C0")) matchCount++;
|
|
5765
|
+
}
|
|
5766
|
+
if (config.rules.requireWorkflow) {
|
|
5767
|
+
totalRules++;
|
|
5768
|
+
if (content.includes("plan") && content.includes("review")) matchCount++;
|
|
5769
|
+
}
|
|
5770
|
+
if (config.rules.prMergeRequiresReview) {
|
|
5771
|
+
totalRules++;
|
|
5772
|
+
if (content.includes("\uB9AC\uBDF0") || content.includes("review")) matchCount++;
|
|
5773
|
+
}
|
|
5774
|
+
if (config.rules.branchProtection) {
|
|
5775
|
+
totalRules++;
|
|
5776
|
+
if (content.includes("main") || content.includes("\uBE0C\uB79C\uCE58")) matchCount++;
|
|
5777
|
+
}
|
|
5778
|
+
if (content.includes("<!-- harness:start -->")) {
|
|
5779
|
+
matchCount++;
|
|
5780
|
+
totalRules++;
|
|
5781
|
+
}
|
|
5782
|
+
if (totalRules === 0) return 10;
|
|
5783
|
+
return Math.round(matchCount / totalRules * 10);
|
|
5784
|
+
} catch {
|
|
5785
|
+
return 0;
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
async function readPackageJson(cwd) {
|
|
5789
|
+
const pkgPath = join16(cwd, "package.json");
|
|
5790
|
+
if (!existsSync8(pkgPath)) return null;
|
|
5791
|
+
try {
|
|
5792
|
+
const raw = await readFile4(pkgPath, "utf8");
|
|
5793
|
+
return JSON.parse(raw);
|
|
5794
|
+
} catch {
|
|
5795
|
+
return null;
|
|
5796
|
+
}
|
|
5797
|
+
}
|
|
5798
|
+
var init_evaluator = __esm({
|
|
5799
|
+
"src/vibe-ready/evaluator.ts"() {
|
|
5800
|
+
"use strict";
|
|
5801
|
+
}
|
|
5802
|
+
});
|
|
5803
|
+
|
|
4953
5804
|
// src/stages/evaluation/analyzer.ts
|
|
4954
5805
|
var analyzer_exports2 = {};
|
|
4955
5806
|
__export(analyzer_exports2, {
|
|
4956
5807
|
analyzeRepository: () => analyzeRepository
|
|
4957
5808
|
});
|
|
4958
|
-
import { join as
|
|
5809
|
+
import { join as join17 } from "path";
|
|
4959
5810
|
import fs17 from "fs-extra";
|
|
4960
5811
|
async function analyzeRepository(repoPath) {
|
|
4961
5812
|
try {
|
|
@@ -5017,14 +5868,14 @@ async function checkTestCoverage(repoPath) {
|
|
|
5017
5868
|
".nycrc"
|
|
5018
5869
|
];
|
|
5019
5870
|
for (const f of testConfigs) {
|
|
5020
|
-
const found = await fs17.pathExists(
|
|
5871
|
+
const found = await fs17.pathExists(join17(repoPath, f));
|
|
5021
5872
|
findings.push({ item: f, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
5022
5873
|
if (found) score += 20;
|
|
5023
5874
|
}
|
|
5024
5875
|
const testDirs = ["tests", "test", "__tests__", "spec"];
|
|
5025
5876
|
let hasTestDir = false;
|
|
5026
5877
|
for (const d of testDirs) {
|
|
5027
|
-
if (await fs17.pathExists(
|
|
5878
|
+
if (await fs17.pathExists(join17(repoPath, d))) {
|
|
5028
5879
|
findings.push({ item: d, found: true, details: "\uD14C\uC2A4\uD2B8 \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC" });
|
|
5029
5880
|
hasTestDir = true;
|
|
5030
5881
|
score += 30;
|
|
@@ -5048,7 +5899,7 @@ async function checkCiCd(repoPath) {
|
|
|
5048
5899
|
{ path: ".circleci", label: "CircleCI" }
|
|
5049
5900
|
];
|
|
5050
5901
|
for (const { path: path10, label } of ciConfigs) {
|
|
5051
|
-
const found = await fs17.pathExists(
|
|
5902
|
+
const found = await fs17.pathExists(join17(repoPath, path10));
|
|
5052
5903
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
5053
5904
|
if (found) score += 40;
|
|
5054
5905
|
}
|
|
@@ -5067,7 +5918,7 @@ async function checkHooks(repoPath) {
|
|
|
5067
5918
|
{ path: ".claude/settings.json", label: "Claude Code settings" }
|
|
5068
5919
|
];
|
|
5069
5920
|
for (const { path: path10, label } of hookConfigs) {
|
|
5070
|
-
const found = await fs17.pathExists(
|
|
5921
|
+
const found = await fs17.pathExists(join17(repoPath, path10));
|
|
5071
5922
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
5072
5923
|
if (found) score += 20;
|
|
5073
5924
|
}
|
|
@@ -5085,7 +5936,7 @@ async function checkRepoStructure(repoPath) {
|
|
|
5085
5936
|
{ path: ".gitignore", label: ".gitignore" }
|
|
5086
5937
|
];
|
|
5087
5938
|
for (const { path: path10, label } of structureChecks) {
|
|
5088
|
-
const found = await fs17.pathExists(
|
|
5939
|
+
const found = await fs17.pathExists(join17(repoPath, path10));
|
|
5089
5940
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
5090
5941
|
if (found) score += 25;
|
|
5091
5942
|
}
|
|
@@ -5102,7 +5953,7 @@ async function checkDocumentation(repoPath) {
|
|
|
5102
5953
|
{ path: "docs/openspec.md", label: "OpenSpec PRD", points: 25 }
|
|
5103
5954
|
];
|
|
5104
5955
|
for (const { path: path10, label, points } of docChecks) {
|
|
5105
|
-
const found = await fs17.pathExists(
|
|
5956
|
+
const found = await fs17.pathExists(join17(repoPath, path10));
|
|
5106
5957
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
5107
5958
|
if (found) score += points;
|
|
5108
5959
|
}
|
|
@@ -5110,6 +5961,54 @@ async function checkDocumentation(repoPath) {
|
|
|
5110
5961
|
return { name: "\uBB38\uC11C\uD654 \uC218\uC900", tier: "nice", score, recommendations: [], rawFindings: findings };
|
|
5111
5962
|
}
|
|
5112
5963
|
async function checkHarnessEngineering(repoPath) {
|
|
5964
|
+
if (await fs17.pathExists(join17(repoPath, ".harness.json"))) {
|
|
5965
|
+
return checkHarnessWithPolicy(repoPath);
|
|
5966
|
+
}
|
|
5967
|
+
return checkHarnessLegacy(repoPath);
|
|
5968
|
+
}
|
|
5969
|
+
async function checkHarnessWithPolicy(repoPath) {
|
|
5970
|
+
const { evaluateHarness: evaluateHarness2 } = await Promise.resolve().then(() => (init_evaluator(), evaluator_exports));
|
|
5971
|
+
const evaluation = await evaluateHarness2(repoPath);
|
|
5972
|
+
const findings = [];
|
|
5973
|
+
findings.push({
|
|
5974
|
+
item: ".harness.json",
|
|
5975
|
+
found: evaluation.configFound,
|
|
5976
|
+
details: evaluation.configFound ? `\uC2DC\uB098\uB9AC\uC624: ${evaluation.scenario}` : "\uC5C6\uC74C"
|
|
5977
|
+
});
|
|
5978
|
+
for (const gate of evaluation.gates) {
|
|
5979
|
+
findings.push({
|
|
5980
|
+
item: `gate:${gate.gate}`,
|
|
5981
|
+
found: gate.status === "pass" || gate.status === "skip",
|
|
5982
|
+
details: gate.status === "pass" ? "\uD1B5\uACFC" : gate.status === "skip" ? "\uBE44\uD65C\uC131 (OK)" : gate.findings.join(", ")
|
|
5983
|
+
});
|
|
5984
|
+
}
|
|
5985
|
+
for (const script of evaluation.missingScripts) {
|
|
5986
|
+
findings.push({ item: `script:${script}`, found: false, details: "\uB204\uB77D" });
|
|
5987
|
+
}
|
|
5988
|
+
const recommendations = [];
|
|
5989
|
+
if (evaluation.missingScripts.length > 0) {
|
|
5990
|
+
recommendations.push({
|
|
5991
|
+
severity: "warning",
|
|
5992
|
+
message: `\uD544\uC218 script ${evaluation.missingScripts.length}\uAC1C \uB204\uB77D`,
|
|
5993
|
+
action: `package.json\uC5D0 \uCD94\uAC00: ${evaluation.missingScripts.join(", ")}`
|
|
5994
|
+
});
|
|
5995
|
+
}
|
|
5996
|
+
if (evaluation.missingFiles.length > 0) {
|
|
5997
|
+
recommendations.push({
|
|
5998
|
+
severity: "warning",
|
|
5999
|
+
message: `Harness \uD30C\uC77C ${evaluation.missingFiles.length}\uAC1C \uB204\uB77D`,
|
|
6000
|
+
action: `roboco scaffold \uC2E4\uD589 \uB610\uB294 \uC218\uB3D9 \uC0DD\uC131: ${evaluation.missingFiles.join(", ")}`
|
|
6001
|
+
});
|
|
6002
|
+
}
|
|
6003
|
+
return {
|
|
6004
|
+
name: "\uD558\uB124\uC2A4 \uC5D4\uC9C0\uB2C8\uC5B4\uB9C1",
|
|
6005
|
+
tier: "nice",
|
|
6006
|
+
score: evaluation.score,
|
|
6007
|
+
recommendations,
|
|
6008
|
+
rawFindings: findings
|
|
6009
|
+
};
|
|
6010
|
+
}
|
|
6011
|
+
async function checkHarnessLegacy(repoPath) {
|
|
5113
6012
|
const findings = [];
|
|
5114
6013
|
let score = 0;
|
|
5115
6014
|
const harnessChecks = [
|
|
@@ -5121,12 +6020,20 @@ async function checkHarnessEngineering(repoPath) {
|
|
|
5121
6020
|
{ path: ".pai/config.json", label: "PAI config", points: 10 }
|
|
5122
6021
|
];
|
|
5123
6022
|
for (const { path: path10, label, points } of harnessChecks) {
|
|
5124
|
-
const found = await fs17.pathExists(
|
|
6023
|
+
const found = await fs17.pathExists(join17(repoPath, path10));
|
|
5125
6024
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
5126
6025
|
if (found) score += points;
|
|
5127
6026
|
}
|
|
5128
6027
|
score = Math.min(100, score);
|
|
5129
|
-
|
|
6028
|
+
const recommendations = [];
|
|
6029
|
+
if (!await fs17.pathExists(join17(repoPath, ".harness.json"))) {
|
|
6030
|
+
recommendations.push({
|
|
6031
|
+
severity: "info",
|
|
6032
|
+
message: ".harness.json \uBBF8\uAC10\uC9C0",
|
|
6033
|
+
action: "roboco scaffold\uB97C \uC2E4\uD589\uD558\uBA74 \uC120\uC5B8\uC801 \uC815\uCC45 \uAE30\uBC18 \uD3C9\uAC00\uB85C \uC804\uD658\uB429\uB2C8\uB2E4"
|
|
6034
|
+
});
|
|
6035
|
+
}
|
|
6036
|
+
return { name: "\uD558\uB124\uC2A4 \uC5D4\uC9C0\uB2C8\uC5B4\uB9C1", tier: "nice", score, recommendations, rawFindings: findings };
|
|
5130
6037
|
}
|
|
5131
6038
|
var init_analyzer2 = __esm({
|
|
5132
6039
|
"src/stages/evaluation/analyzer.ts"() {
|
|
@@ -5504,7 +6411,7 @@ __export(shell_cd_exports, {
|
|
|
5504
6411
|
installShellHelper: () => installShellHelper,
|
|
5505
6412
|
requestCdAfter: () => requestCdAfter
|
|
5506
6413
|
});
|
|
5507
|
-
import { join as
|
|
6414
|
+
import { join as join18 } from "path";
|
|
5508
6415
|
import { homedir as homedir2 } from "os";
|
|
5509
6416
|
import fs18 from "fs-extra";
|
|
5510
6417
|
async function requestCdAfter(targetDir) {
|
|
@@ -5541,7 +6448,7 @@ async function installPowerShellHelper() {
|
|
|
5541
6448
|
await fs18.writeFile(HELPER_FILE_PS1, POWERSHELL_HELPER);
|
|
5542
6449
|
const rcFile = getShellRcPath();
|
|
5543
6450
|
const sourceLine = '. "$env:USERPROFILE\\.pai\\shell-helper.ps1"';
|
|
5544
|
-
await fs18.ensureDir(
|
|
6451
|
+
await fs18.ensureDir(join18(rcFile, ".."));
|
|
5545
6452
|
if (await fs18.pathExists(rcFile)) {
|
|
5546
6453
|
const content = await fs18.readFile(rcFile, "utf8");
|
|
5547
6454
|
if (content.includes("shell-helper.ps1")) {
|
|
@@ -5562,10 +6469,10 @@ var init_shell_cd = __esm({
|
|
|
5562
6469
|
"src/utils/shell-cd.ts"() {
|
|
5563
6470
|
"use strict";
|
|
5564
6471
|
init_platform();
|
|
5565
|
-
PAI_DIR =
|
|
5566
|
-
CD_FILE =
|
|
5567
|
-
HELPER_FILE_SH =
|
|
5568
|
-
HELPER_FILE_PS1 =
|
|
6472
|
+
PAI_DIR = join18(homedir2(), ".pai");
|
|
6473
|
+
CD_FILE = join18(PAI_DIR, ".cd-after");
|
|
6474
|
+
HELPER_FILE_SH = join18(PAI_DIR, "shell-helper.sh");
|
|
6475
|
+
HELPER_FILE_PS1 = join18(PAI_DIR, "shell-helper.ps1");
|
|
5569
6476
|
BASH_HELPER = `# PAI shell helper \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9 \uC9C0\uC6D0
|
|
5570
6477
|
pai() {
|
|
5571
6478
|
local cd_target="$HOME/.pai/.cd-after"
|
|
@@ -5700,7 +6607,7 @@ function mergeOmcIntoSettings(settings, marketplaceId, marketplaceUrl, pluginId)
|
|
|
5700
6607
|
return next;
|
|
5701
6608
|
}
|
|
5702
6609
|
var DEFAULT_MARKETPLACE_ID, DEFAULT_MARKETPLACE_URL, DEFAULT_PLUGIN_ID, ClaudeSettingsError;
|
|
5703
|
-
var
|
|
6610
|
+
var init_claude_settings2 = __esm({
|
|
5704
6611
|
"src/utils/claude-settings.ts"() {
|
|
5705
6612
|
"use strict";
|
|
5706
6613
|
init_platform();
|
|
@@ -5719,13 +6626,13 @@ var init_claude_settings = __esm({
|
|
|
5719
6626
|
});
|
|
5720
6627
|
|
|
5721
6628
|
// src/stages/evaluation/cache.ts
|
|
5722
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
5723
|
-
import { join as
|
|
6629
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync as existsSync9 } from "fs";
|
|
6630
|
+
import { join as join19 } from "path";
|
|
5724
6631
|
import { createHash } from "crypto";
|
|
5725
6632
|
function computeRepoHash(repoPath) {
|
|
5726
6633
|
const hash = createHash("sha256");
|
|
5727
6634
|
for (const file of FILES_TO_HASH) {
|
|
5728
|
-
const fullPath =
|
|
6635
|
+
const fullPath = join19(repoPath, file);
|
|
5729
6636
|
try {
|
|
5730
6637
|
const content = readFileSync(fullPath);
|
|
5731
6638
|
hash.update(`${file}:${content.length}`);
|
|
@@ -5736,7 +6643,7 @@ function computeRepoHash(repoPath) {
|
|
|
5736
6643
|
return hash.digest("hex").slice(0, 16);
|
|
5737
6644
|
}
|
|
5738
6645
|
function getCachePath(repoPath) {
|
|
5739
|
-
return
|
|
6646
|
+
return join19(repoPath, CACHE_DIR, CACHE_FILE);
|
|
5740
6647
|
}
|
|
5741
6648
|
function loadCache(repoPath) {
|
|
5742
6649
|
try {
|
|
@@ -5747,8 +6654,8 @@ function loadCache(repoPath) {
|
|
|
5747
6654
|
}
|
|
5748
6655
|
}
|
|
5749
6656
|
function saveCache(repoPath, store) {
|
|
5750
|
-
const cacheDir =
|
|
5751
|
-
if (!
|
|
6657
|
+
const cacheDir = join19(repoPath, CACHE_DIR, "cache");
|
|
6658
|
+
if (!existsSync9(cacheDir)) {
|
|
5752
6659
|
mkdirSync(cacheDir, { recursive: true });
|
|
5753
6660
|
}
|
|
5754
6661
|
writeFileSync(getCachePath(repoPath), JSON.stringify(store, null, 2));
|
|
@@ -5806,7 +6713,7 @@ var evaluate_cmd_exports = {};
|
|
|
5806
6713
|
__export(evaluate_cmd_exports, {
|
|
5807
6714
|
evaluateCommand: () => evaluateCommand
|
|
5808
6715
|
});
|
|
5809
|
-
import { join as
|
|
6716
|
+
import { join as join20, basename } from "path";
|
|
5810
6717
|
import fs20 from "fs-extra";
|
|
5811
6718
|
async function evaluateCommand(cwd, options) {
|
|
5812
6719
|
const useCache = options.cache !== false;
|
|
@@ -5830,8 +6737,8 @@ async function evaluateCommand(cwd, options) {
|
|
|
5830
6737
|
const config = await loadConfig(cwd);
|
|
5831
6738
|
const projectName = config?.projectName ?? basename(cwd);
|
|
5832
6739
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5833
|
-
const reportDir =
|
|
5834
|
-
const reportPath =
|
|
6740
|
+
const reportDir = join20(cwd, "docs", "p-reports");
|
|
6741
|
+
const reportPath = join20(reportDir, `${today}.md`);
|
|
5835
6742
|
await fs20.ensureDir(reportDir);
|
|
5836
6743
|
const detailedReport = buildDetailedReport(result, projectName);
|
|
5837
6744
|
await fs20.writeFile(reportPath, detailedReport, "utf8");
|
|
@@ -6091,7 +6998,7 @@ var init_cmd_exports = {};
|
|
|
6091
6998
|
__export(init_cmd_exports, {
|
|
6092
6999
|
initCommand: () => initCommand
|
|
6093
7000
|
});
|
|
6094
|
-
import { join as
|
|
7001
|
+
import { join as join21, basename as basename2 } from "path";
|
|
6095
7002
|
import fs21 from "fs-extra";
|
|
6096
7003
|
async function initCommand(cwd, nameArg) {
|
|
6097
7004
|
printWelcomeBanner();
|
|
@@ -6150,11 +7057,11 @@ async function initCommand(cwd, nameArg) {
|
|
|
6150
7057
|
const evalResult = computeResult2(llmOutput);
|
|
6151
7058
|
printReport2(evalResult);
|
|
6152
7059
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6153
|
-
const reportDir =
|
|
7060
|
+
const reportDir = join21(cwd, "docs", "p-reports");
|
|
6154
7061
|
await fs21.ensureDir(reportDir);
|
|
6155
7062
|
const legacyName = basename2(cwd);
|
|
6156
7063
|
const detailedReport = buildDetailedReport3(evalResult, legacyName);
|
|
6157
|
-
await fs21.writeFile(
|
|
7064
|
+
await fs21.writeFile(join21(reportDir, `${today}.md`), detailedReport, "utf8");
|
|
6158
7065
|
console.log("");
|
|
6159
7066
|
success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
|
|
6160
7067
|
} catch {
|
|
@@ -6174,7 +7081,7 @@ async function initCommand(cwd, nameArg) {
|
|
|
6174
7081
|
return;
|
|
6175
7082
|
}
|
|
6176
7083
|
}
|
|
6177
|
-
step(1,
|
|
7084
|
+
step(1, TOTAL_STEPS, "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815");
|
|
6178
7085
|
hint("\uC774 \uC774\uB984\uC73C\uB85C \uD504\uB85C\uC81D\uD2B8 \uD3F4\uB354\uAC00 \uC0DD\uC131\uB429\uB2C8\uB2E4.");
|
|
6179
7086
|
hint("\uC601\uBB38 \uC18C\uBB38\uC790, \uC22B\uC790, \uD558\uC774\uD508(-)\uC744 \uC0AC\uC6A9\uD574\uC8FC\uC138\uC694.");
|
|
6180
7087
|
console.log("");
|
|
@@ -6199,7 +7106,7 @@ async function initCommand(cwd, nameArg) {
|
|
|
6199
7106
|
}]);
|
|
6200
7107
|
projectName = answer.name.trim();
|
|
6201
7108
|
}
|
|
6202
|
-
const projectDir =
|
|
7109
|
+
const projectDir = join21(cwd, projectName);
|
|
6203
7110
|
if (await fs21.pathExists(projectDir)) {
|
|
6204
7111
|
const existingConfig = await loadConfig(projectDir);
|
|
6205
7112
|
if (existingConfig) {
|
|
@@ -6226,7 +7133,6 @@ async function setupInDirectory(projectDir, projectName) {
|
|
|
6226
7133
|
hint("\uC0C8 \uD130\uBBF8\uB110\uC5D0\uC11C pai \uBA85\uB839\uC73C\uB85C \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9\uC774 \uD65C\uC131\uD654\uB429\uB2C8\uB2E4.");
|
|
6227
7134
|
console.log("");
|
|
6228
7135
|
}
|
|
6229
|
-
step(2, 3, "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815");
|
|
6230
7136
|
const config = createDefaultConfig(projectName, "prototype");
|
|
6231
7137
|
const input = {
|
|
6232
7138
|
cwd: projectDir,
|
|
@@ -6259,55 +7165,55 @@ async function setupInDirectory(projectDir, projectName) {
|
|
|
6259
7165
|
}
|
|
6260
7166
|
const interview = result.data.interview;
|
|
6261
7167
|
const isCurrentDir = projectDir === process.cwd();
|
|
6262
|
-
await showCompletion(projectName, projectDir, interview
|
|
7168
|
+
await showCompletion(projectName, projectDir, interview, isCurrentDir);
|
|
6263
7169
|
} else {
|
|
6264
7170
|
for (const err of result.errors) {
|
|
6265
7171
|
error(err.message);
|
|
6266
7172
|
}
|
|
6267
7173
|
}
|
|
6268
7174
|
}
|
|
6269
|
-
async function showCompletion(projectName, projectDir,
|
|
7175
|
+
async function showCompletion(projectName, projectDir, interview, isCurrentDir) {
|
|
6270
7176
|
const { default: inquirer } = await import("inquirer");
|
|
6271
7177
|
const chalk9 = (await import("chalk")).default;
|
|
6272
7178
|
const { sleep: sleep2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
|
|
6273
|
-
|
|
6274
|
-
|
|
7179
|
+
const extraTools = interview.extraTools;
|
|
7180
|
+
step(4, TOTAL_STEPS, "\uC124\uCE58 \uD6C4 \uC548\uB0B4");
|
|
7181
|
+
console.log(colors.success(" \uD504\uB85C\uC81D\uD2B8 \uC900\uBE44\uAC00 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4!"));
|
|
7182
|
+
console.log(colors.dim(" \uC774\uC81C \uBC14\uB85C \uAD6C\uD604\uC744 \uC2DC\uC791\uD558\uAC70\uB098, \uD544\uC694\uD55C \uD658\uACBD \uBCC0\uC218\uB9CC \uCC44\uC6B0\uBA74 \uB429\uB2C8\uB2E4."));
|
|
6275
7183
|
console.log("");
|
|
6276
|
-
console.log(colors.
|
|
7184
|
+
console.log(colors.accent(" \uC124\uCE58\uB41C \uAE30\uB2A5"));
|
|
6277
7185
|
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6278
|
-
console.log(` ${
|
|
6279
|
-
console.log(` ${
|
|
6280
|
-
console.log(` ${
|
|
6281
|
-
console.log(` ${
|
|
6282
|
-
console.log("");
|
|
6283
|
-
console.log(colors.accent(" \uC124\uCE58\uB41C \uD50C\uB7EC\uADF8\uC778 & \uAE30\uB2A5"));
|
|
6284
|
-
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6285
|
-
console.log(` ${chalk9.cyan("OpenSpec+OMC")} \uC124\uACC4\uB3C4 & \uC9C0\uD615\uB3C4 \u2014 \uBB34\uC5C7\uC744, \uC5B4\uB514\uC5D0 \uB9CC\uB4E4\uC9C0 \uC815\uC758`);
|
|
6286
|
-
console.log(` ${chalk9.cyan("RoboCo CLI")} \uD604\uC7A5 \uC18C\uC7A5 \u2014 \uC804\uCCB4 \uACF5\uC815 \uAD00\uB9AC, AI\uC5D0\uAC8C \uC791\uC5C5 \uC9C0\uC2DC`);
|
|
6287
|
-
console.log(` ${chalk9.cyan("Vibe-Ready")} \uD488\uC9C8 \uAC80\uC218 \u2014 AI \uAC1C\uBC1C \uC900\uBE44\uB3C4 6\uCE74\uD14C\uACE0\uB9AC \uD3C9\uAC00`);
|
|
7186
|
+
console.log(` ${chalk9.cyan("OpenSpec")} PRD \uC124\uACC4 \uD15C\uD50C\uB9BF`);
|
|
7187
|
+
console.log(` ${chalk9.cyan("RoboCo")} \uD30C\uC774\uD504\uB77C\uC778 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158`);
|
|
7188
|
+
console.log(` ${chalk9.cyan("Harness")} \uD488\uC9C8 \uC815\uCC45 (hook \xB7 CI \xB7 lint)`);
|
|
7189
|
+
console.log(` ${chalk9.cyan("Vibe-Ready")} AI \uAC1C\uBC1C \uC900\uBE44\uB3C4 \uC9C4\uB2E8`);
|
|
6288
7190
|
if (extraTools.includes("omc")) {
|
|
6289
|
-
console.log(` ${chalk9.cyan("OMC")}
|
|
7191
|
+
console.log(` ${chalk9.cyan("OMC")} AI \uC5D0\uC774\uC804\uD2B8 \uC791\uC5C5 \uBD84\uC5C5`);
|
|
6290
7192
|
}
|
|
6291
7193
|
if (extraTools.includes("gstack")) {
|
|
6292
|
-
console.log(` ${chalk9.cyan("gstack")}
|
|
6293
|
-
}
|
|
6294
|
-
if (extraTools.includes("harness")) {
|
|
6295
|
-
console.log(` ${chalk9.cyan("Harness")} \uC548\uC804\uC7A5\uCE58 & \uAC80\uC0AC\uB300 \u2014 AI \uCF54\uB4DC \uC791\uB3D9 \uD14C\uC2A4\uD2B8`);
|
|
7194
|
+
console.log(` ${chalk9.cyan("gstack")} \uD14C\uC2A4\uD2B8 \uC790\uB3D9\uD654`);
|
|
6296
7195
|
}
|
|
6297
7196
|
if (extraTools.includes("vercel")) {
|
|
6298
|
-
console.log(` ${chalk9.cyan("Vercel")}
|
|
7197
|
+
console.log(` ${chalk9.cyan("Vercel")} \uC790\uB3D9 \uBC30\uD3EC (Preview + Production)`);
|
|
6299
7198
|
}
|
|
6300
7199
|
if (extraTools.includes("supabase")) {
|
|
6301
|
-
console.log(` ${chalk9.cyan("Supabase")}
|
|
7200
|
+
console.log(` ${chalk9.cyan("Supabase")} DB + \uC778\uC99D + \uC2A4\uD1A0\uB9AC\uC9C0`);
|
|
6302
7201
|
}
|
|
6303
|
-
if (extraTools.includes("mcp")) {
|
|
6304
|
-
console.log(` ${chalk9.cyan("MCP \uC11C\uBC84")}
|
|
7202
|
+
if (extraTools.includes("mcp") && interview.mcp) {
|
|
7203
|
+
console.log(` ${chalk9.cyan("MCP \uC11C\uBC84")} ${interview.mcp.name} (${interview.mcp.type})`);
|
|
7204
|
+
}
|
|
7205
|
+
if (interview.authMethods.length > 0) {
|
|
7206
|
+
console.log(` ${chalk9.cyan("\uB85C\uADF8\uC778")} ${interview.authMethods.join(", ")}`);
|
|
7207
|
+
}
|
|
7208
|
+
if (interview.recipes && interview.recipes.length > 0) {
|
|
7209
|
+
console.log(` ${chalk9.cyan("\uC0AC\uB0B4 \uC5F0\uB3D9")} ${interview.recipes.join(", ")}`);
|
|
6305
7210
|
}
|
|
6306
7211
|
console.log("");
|
|
6307
|
-
console.log(colors.accent(" \
|
|
7212
|
+
console.log(colors.accent(" \uD30C\uC77C \uD655\uC778"));
|
|
6308
7213
|
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6309
7214
|
const checks = [
|
|
6310
7215
|
{ label: "AI \uCEE8\uD14D\uC2A4\uD2B8", path: "CLAUDE.md" },
|
|
7216
|
+
{ label: "Harness \uC815\uCC45", path: ".harness.json" },
|
|
6311
7217
|
{ label: "PRD \uD15C\uD50C\uB9BF", path: "docs/openspec.md" },
|
|
6312
7218
|
{ label: "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815", path: ".pai/config.json" },
|
|
6313
7219
|
{ label: "Claude \uC124\uC815", path: ".claude/settings.json" },
|
|
@@ -6315,10 +7221,42 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
|
|
|
6315
7221
|
...extraTools.includes("omc") ? [{ label: "OMC \uB7F0\uD0C0\uC784", path: ".omc" }] : []
|
|
6316
7222
|
];
|
|
6317
7223
|
for (const check of checks) {
|
|
6318
|
-
const exists = await fs21.pathExists(
|
|
7224
|
+
const exists = await fs21.pathExists(join21(projectDir, check.path));
|
|
6319
7225
|
console.log(` ${exists ? colors.success("\u2713") : colors.err("\u2717")} ${check.label.padEnd(16)} ${colors.dim(check.path)}`);
|
|
6320
7226
|
}
|
|
6321
7227
|
console.log("");
|
|
7228
|
+
console.log(colors.accent(" \uB2E4\uC74C \uD560 \uC77C"));
|
|
7229
|
+
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
7230
|
+
if (interview.authMethods.length > 0) {
|
|
7231
|
+
if (interview.authMethods.includes("custom") && !interview.customAuth?.clientId) {
|
|
7232
|
+
console.log(` ${chalk9.yellow("\u2192")} .env.local \uC5D0 OAuth \uD0A4\uB97C \uCC44\uC6CC\uC8FC\uC138\uC694`);
|
|
7233
|
+
}
|
|
7234
|
+
if (interview.authMethods.includes("google")) {
|
|
7235
|
+
console.log(` ${chalk9.yellow("\u2192")} Google Cloud Console\uC5D0\uC11C OAuth \uD074\uB77C\uC774\uC5B8\uD2B8 \uC0DD\uC131`);
|
|
7236
|
+
}
|
|
7237
|
+
if (interview.authMethods.includes("kakao")) {
|
|
7238
|
+
console.log(` ${chalk9.yellow("\u2192")} Kakao Developers\uC5D0\uC11C \uC571 \uB4F1\uB85D`);
|
|
7239
|
+
}
|
|
7240
|
+
if (interview.authMethods.includes("naver")) {
|
|
7241
|
+
console.log(` ${chalk9.yellow("\u2192")} Naver Developers\uC5D0\uC11C \uC571 \uB4F1\uB85D`);
|
|
7242
|
+
}
|
|
7243
|
+
}
|
|
7244
|
+
if (extraTools.includes("vercel")) {
|
|
7245
|
+
console.log(` ${chalk9.yellow("\u2192")} vercel login \uC73C\uB85C Vercel \uACC4\uC815 \uC5F0\uACB0`);
|
|
7246
|
+
}
|
|
7247
|
+
if (extraTools.includes("supabase")) {
|
|
7248
|
+
console.log(` ${chalk9.yellow("\u2192")} .env.local \uC5D0 SUPABASE_URL, SUPABASE_ANON_KEY \uC785\uB825`);
|
|
7249
|
+
}
|
|
7250
|
+
if (extraTools.includes("mcp") && interview.mcp) {
|
|
7251
|
+
console.log(` ${chalk9.yellow("\u2192")} mcp-server/ \uC5D0\uC11C MCP \uC11C\uBC84 \uAC1C\uBC1C \uC2DC\uC791`);
|
|
7252
|
+
}
|
|
7253
|
+
if (interview.recipes && interview.recipes.length > 0) {
|
|
7254
|
+
for (const recipe of interview.recipes) {
|
|
7255
|
+
console.log(` ${chalk9.yellow("\u2192")} docs/recipes/${recipe}/ \uAC00\uC774\uB4DC \uD655\uC778`);
|
|
7256
|
+
}
|
|
7257
|
+
}
|
|
7258
|
+
console.log(` ${chalk9.yellow("\u2192")} docs/openspec.md \uC5D0 PRD \uC791\uC131 \uC2DC\uC791`);
|
|
7259
|
+
console.log("");
|
|
6322
7260
|
const { runEval } = await inquirer.prompt([{
|
|
6323
7261
|
type: "confirm",
|
|
6324
7262
|
name: "runEval",
|
|
@@ -6340,10 +7278,10 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
|
|
|
6340
7278
|
printReport2(evalResult);
|
|
6341
7279
|
await sleep2(500);
|
|
6342
7280
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6343
|
-
const reportDir =
|
|
7281
|
+
const reportDir = join21(projectDir, "docs", "p-reports");
|
|
6344
7282
|
await fs21.ensureDir(reportDir);
|
|
6345
7283
|
const detailedReport = buildDetailedReport3(evalResult, projectName);
|
|
6346
|
-
await fs21.writeFile(
|
|
7284
|
+
await fs21.writeFile(join21(reportDir, `${today}.md`), detailedReport, "utf8");
|
|
6347
7285
|
await sleep2(500);
|
|
6348
7286
|
console.log("");
|
|
6349
7287
|
success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
|
|
@@ -6371,7 +7309,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
|
|
|
6371
7309
|
const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
|
|
6372
7310
|
const omcSpinner = createSpinner2("OMC \uD50C\uB7EC\uADF8\uC778 \uB4F1\uB85D \uC911...");
|
|
6373
7311
|
try {
|
|
6374
|
-
const { enableOmcPlugin: enableOmcPlugin2 } = await Promise.resolve().then(() => (
|
|
7312
|
+
const { enableOmcPlugin: enableOmcPlugin2 } = await Promise.resolve().then(() => (init_claude_settings2(), claude_settings_exports));
|
|
6375
7313
|
const result = await enableOmcPlugin2();
|
|
6376
7314
|
if (result.action === "already-enabled") {
|
|
6377
7315
|
omcSpinner.succeed("OMC \uD50C\uB7EC\uADF8\uC778 \uC774\uBBF8 \uB4F1\uB85D\uB428");
|
|
@@ -6416,7 +7354,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
|
|
|
6416
7354
|
const aliasLine = getYoloAliasLine2();
|
|
6417
7355
|
const rcContent = await fs21.readFile(shellRc, "utf8").catch(() => "");
|
|
6418
7356
|
if (!rcContent.includes("claude-yolo")) {
|
|
6419
|
-
await fs21.ensureDir(
|
|
7357
|
+
await fs21.ensureDir(join21(shellRc, ".."));
|
|
6420
7358
|
await fs21.appendFile(shellRc, `
|
|
6421
7359
|
# PAI \u2014 claude-YOLO mode
|
|
6422
7360
|
${aliasLine}
|
|
@@ -6610,9 +7548,9 @@ async function installOrchestratorOnly(projectDir, projectName) {
|
|
|
6610
7548
|
const evalResult = computeResult2(llmOutput);
|
|
6611
7549
|
printReport2(evalResult);
|
|
6612
7550
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6613
|
-
const reportDir =
|
|
7551
|
+
const reportDir = join21(projectDir, "docs", "p-reports");
|
|
6614
7552
|
await fs21.ensureDir(reportDir);
|
|
6615
|
-
await fs21.writeFile(
|
|
7553
|
+
await fs21.writeFile(join21(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
|
|
6616
7554
|
console.log("");
|
|
6617
7555
|
hint(`\uC0C1\uC138 \uB9AC\uD3EC\uD2B8: docs/p-reports/${today}.md`);
|
|
6618
7556
|
} catch {
|
|
@@ -6662,7 +7600,7 @@ async function detectLegacyProject(cwd) {
|
|
|
6662
7600
|
".gitignore"
|
|
6663
7601
|
];
|
|
6664
7602
|
for (const signal of signals) {
|
|
6665
|
-
if (await fs21.pathExists(
|
|
7603
|
+
if (await fs21.pathExists(join21(cwd, signal))) return true;
|
|
6666
7604
|
}
|
|
6667
7605
|
return false;
|
|
6668
7606
|
}
|
|
@@ -6675,6 +7613,7 @@ var init_init_cmd = __esm({
|
|
|
6675
7613
|
init_detector();
|
|
6676
7614
|
init_roboco();
|
|
6677
7615
|
init_validate_cmd();
|
|
7616
|
+
init_interviewer();
|
|
6678
7617
|
init_ui();
|
|
6679
7618
|
init_logger();
|
|
6680
7619
|
}
|
|
@@ -6755,12 +7694,12 @@ var init_help_cmd = __esm({
|
|
|
6755
7694
|
});
|
|
6756
7695
|
|
|
6757
7696
|
// src/stages/design/openspec.ts
|
|
6758
|
-
import { join as
|
|
7697
|
+
import { join as join22 } from "path";
|
|
6759
7698
|
import fs22 from "fs-extra";
|
|
6760
7699
|
async function initOpenSpec(cwd, projectName) {
|
|
6761
|
-
const docsDir =
|
|
7700
|
+
const docsDir = join22(cwd, "docs");
|
|
6762
7701
|
await fs22.ensureDir(docsDir);
|
|
6763
|
-
const openspecPath =
|
|
7702
|
+
const openspecPath = join22(docsDir, "openspec.md");
|
|
6764
7703
|
if (await fs22.pathExists(openspecPath)) {
|
|
6765
7704
|
info("docs/openspec.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
|
|
6766
7705
|
return;
|
|
@@ -6793,9 +7732,9 @@ async function initOpenSpec(cwd, projectName) {
|
|
|
6793
7732
|
}
|
|
6794
7733
|
async function validateOpenSpec(cwd) {
|
|
6795
7734
|
const candidates = [
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
7735
|
+
join22(cwd, "docs", "openspec.md"),
|
|
7736
|
+
join22(cwd, "openspec.md"),
|
|
7737
|
+
join22(cwd, ".pai", "openspec.md")
|
|
6799
7738
|
];
|
|
6800
7739
|
let specPath = null;
|
|
6801
7740
|
for (const p of candidates) {
|
|
@@ -6861,12 +7800,12 @@ var init_openspec = __esm({
|
|
|
6861
7800
|
});
|
|
6862
7801
|
|
|
6863
7802
|
// src/stages/design/omc.ts
|
|
6864
|
-
import { join as
|
|
7803
|
+
import { join as join23 } from "path";
|
|
6865
7804
|
import fs23 from "fs-extra";
|
|
6866
7805
|
async function initOMC(cwd, projectName) {
|
|
6867
|
-
const paiDir =
|
|
7806
|
+
const paiDir = join23(cwd, ".pai");
|
|
6868
7807
|
await fs23.ensureDir(paiDir);
|
|
6869
|
-
const omcPath =
|
|
7808
|
+
const omcPath = join23(paiDir, "omc.md");
|
|
6870
7809
|
if (await fs23.pathExists(omcPath)) {
|
|
6871
7810
|
info(".pai/omc.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
|
|
6872
7811
|
return;
|
|
@@ -7030,13 +7969,13 @@ var init_context = __esm({
|
|
|
7030
7969
|
});
|
|
7031
7970
|
|
|
7032
7971
|
// src/stages/design/index.ts
|
|
7033
|
-
import { join as
|
|
7972
|
+
import { join as join24 } from "path";
|
|
7034
7973
|
import fs24 from "fs-extra";
|
|
7035
7974
|
async function autoInstallHarness(cwd) {
|
|
7036
|
-
const harnessPath =
|
|
7975
|
+
const harnessPath = join24(cwd, ".pai", "harness.json");
|
|
7037
7976
|
if (await fs24.pathExists(harnessPath)) return;
|
|
7038
7977
|
await withSpinner("Harness Engineering \uC790\uB3D9 \uC124\uC815 \uC911...", async () => {
|
|
7039
|
-
await fs24.ensureDir(
|
|
7978
|
+
await fs24.ensureDir(join24(cwd, ".pai"));
|
|
7040
7979
|
await fs24.writeJson(harnessPath, {
|
|
7041
7980
|
version: "1.0",
|
|
7042
7981
|
specFile: "docs/openspec.md",
|
|
@@ -7753,7 +8692,7 @@ var savetoken_cmd_exports = {};
|
|
|
7753
8692
|
__export(savetoken_cmd_exports, {
|
|
7754
8693
|
savetokenCommand: () => savetokenCommand
|
|
7755
8694
|
});
|
|
7756
|
-
import { join as
|
|
8695
|
+
import { join as join25, relative } from "path";
|
|
7757
8696
|
import fs26 from "fs-extra";
|
|
7758
8697
|
import chalk7 from "chalk";
|
|
7759
8698
|
async function savetokenCommand(cwd) {
|
|
@@ -7823,10 +8762,10 @@ async function savetokenCommand(cwd) {
|
|
|
7823
8762
|
console.log(` \u2192 ${colors.dim("\uB8F0 \uAE30\uBC18 \uB85C\uC9C1, \uD0A4\uC6CC\uB4DC \uB9E4\uCE6D\uC73C\uB85C \uAC80\uD1A0 \uD6C4 \uB300\uCCB4")}`);
|
|
7824
8763
|
console.log(` ${chalk7.red("\u25CF")} \uB192\uC74C \uCF54\uB4DC \uC0DD\uC131, \uBCF5\uC7A1\uD55C \uCD94\uB860, \uCC3D\uC758\uC801 \uC0DD\uC131`);
|
|
7825
8764
|
console.log(` \u2192 ${colors.dim("AI \uD544\uC218 \u2014 \uD504\uB86C\uD504\uD2B8 \uCD5C\uC801\uD654\uB85C \uD1A0\uD070 \uC808\uAC10")}`);
|
|
7826
|
-
const reportDir =
|
|
8765
|
+
const reportDir = join25(cwd, ".pai");
|
|
7827
8766
|
await fs26.ensureDir(reportDir);
|
|
7828
8767
|
const report = buildReport(callSites, cwd);
|
|
7829
|
-
const reportPath =
|
|
8768
|
+
const reportPath = join25(reportDir, "savetoken-report.md");
|
|
7830
8769
|
await fs26.writeFile(reportPath, report, "utf8");
|
|
7831
8770
|
console.log("");
|
|
7832
8771
|
success("\uC2A4\uCE94 \uB9AC\uD3EC\uD2B8 \uC800\uC7A5: .pai/savetoken-report.md");
|
|
@@ -7983,7 +8922,7 @@ var wakeup_cmd_exports = {};
|
|
|
7983
8922
|
__export(wakeup_cmd_exports, {
|
|
7984
8923
|
wakeupCommand: () => wakeupCommand
|
|
7985
8924
|
});
|
|
7986
|
-
import { join as
|
|
8925
|
+
import { join as join26 } from "path";
|
|
7987
8926
|
import { homedir as homedir3, platform as osPlatform } from "os";
|
|
7988
8927
|
import fs27 from "fs-extra";
|
|
7989
8928
|
import chalk8 from "chalk";
|
|
@@ -8064,7 +9003,7 @@ async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
|
|
|
8064
9003
|
async function setupMacOS(config) {
|
|
8065
9004
|
const { execa } = await import("execa");
|
|
8066
9005
|
const [hour, minute] = config.time.split(":").map(Number);
|
|
8067
|
-
const plistDir =
|
|
9006
|
+
const plistDir = join26(homedir3(), "Library", "LaunchAgents");
|
|
8068
9007
|
await fs27.ensureDir(plistDir);
|
|
8069
9008
|
const weekdays = scheduleToWeekdays(config.schedule);
|
|
8070
9009
|
let calendarEntries;
|
|
@@ -8123,9 +9062,9 @@ ${calendarEntries}
|
|
|
8123
9062
|
async function setupWindows(config) {
|
|
8124
9063
|
const { execa } = await import("execa");
|
|
8125
9064
|
const [hour, minute] = config.time.split(":").map(Number);
|
|
8126
|
-
const psScriptDir =
|
|
9065
|
+
const psScriptDir = join26(homedir3(), ".pai");
|
|
8127
9066
|
await fs27.ensureDir(psScriptDir);
|
|
8128
|
-
const psScriptPath =
|
|
9067
|
+
const psScriptPath = join26(psScriptDir, "wakeup.ps1");
|
|
8129
9068
|
const claudeCmd = config.launchMode === "yolo" ? "claude --dangerously-skip-permissions" : "claude";
|
|
8130
9069
|
const psScript = `# PAI Wakeup \u2014 Claude Code \uC138\uC158 \uC790\uB3D9 \uC2DC\uC791
|
|
8131
9070
|
$paiDir = "$env:USERPROFILE\\.pai"
|
|
@@ -8399,12 +9338,12 @@ var init_wakeup_cmd = __esm({
|
|
|
8399
9338
|
"use strict";
|
|
8400
9339
|
init_ui();
|
|
8401
9340
|
init_logger();
|
|
8402
|
-
PAI_DIR2 =
|
|
8403
|
-
CONFIG_FILE2 =
|
|
8404
|
-
MESSAGES_FILE =
|
|
8405
|
-
SCRIPT_FILE =
|
|
9341
|
+
PAI_DIR2 = join26(homedir3(), ".pai");
|
|
9342
|
+
CONFIG_FILE2 = join26(PAI_DIR2, "wakeup-config.json");
|
|
9343
|
+
MESSAGES_FILE = join26(PAI_DIR2, "wakeup-messages.json");
|
|
9344
|
+
SCRIPT_FILE = join26(PAI_DIR2, "wakeup.sh");
|
|
8406
9345
|
PLIST_NAME = "com.pai.wakeup";
|
|
8407
|
-
PLIST_PATH =
|
|
9346
|
+
PLIST_PATH = join26(homedir3(), "Library", "LaunchAgents", `${PLIST_NAME}.plist`);
|
|
8408
9347
|
CRON_MARKER = "# PAI-WAKEUP";
|
|
8409
9348
|
MESSAGES = [
|
|
8410
9349
|
`Here's to the crazy ones. The misfits. The rebels. The troublemakers.
|