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