pai-zero 0.13.2 → 0.14.0

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
+ }
630
+ if (mode === "production") {
631
+ console.log(` ${colors.accent("+")} gstack \uD14C\uC2A4\uD2B8 \xB7 Harness \uD488\uC9C8 \uAC80\uC99D`);
629
632
  }
630
- step(2, 3, "\uB85C\uADF8\uC778 \uAE30\uB2A5");
631
- hint("\uB098\uC911\uC5D0 \uCD94\uAC00\uD560 \uC218\uB3C4 \uC788\uC2B5\uB2C8\uB2E4.");
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");
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."));
632
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
 
@@ -3605,6 +3647,543 @@ var init_detector = __esm({
3605
3647
  }
3606
3648
  });
3607
3649
 
3650
+ // src/core/harness-defaults.ts
3651
+ function resolveConfig(partial) {
3652
+ const base = HARNESS_PRESETS[partial.scenario];
3653
+ if (!partial.overrides) return { ...base };
3654
+ const overrides = partial.overrides;
3655
+ return {
3656
+ ...base,
3657
+ gates: {
3658
+ preCommit: { ...base.gates.preCommit, ...overrides.gates?.preCommit },
3659
+ prePush: { ...base.gates.prePush, ...overrides.gates?.prePush },
3660
+ ci: { ...base.gates.ci, ...overrides.gates?.ci }
3661
+ },
3662
+ scripts: overrides.scripts ?? base.scripts,
3663
+ permissions: overrides.permissions ? {
3664
+ allow: [...base.permissions.allow, ...overrides.permissions.allow ?? []],
3665
+ deny: [...base.permissions.deny, ...overrides.permissions.deny ?? []]
3666
+ } : base.permissions,
3667
+ rules: { ...base.rules, ...overrides.rules }
3668
+ };
3669
+ }
3670
+ 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;
3671
+ var init_harness_defaults = __esm({
3672
+ "src/core/harness-defaults.ts"() {
3673
+ "use strict";
3674
+ SCRIPTS_CORE = [
3675
+ { name: "format", command: "prettier --write .", required: true },
3676
+ { name: "lint", command: "eslint .", required: true },
3677
+ { name: "lint:fix", command: "eslint . --fix", required: false },
3678
+ { name: "typecheck", command: "tsc --noEmit", required: true },
3679
+ { name: "build", command: "tsup", required: true }
3680
+ ];
3681
+ SCRIPTS_TEST_BASIC = [
3682
+ { name: "test", command: "vitest run", required: true }
3683
+ ];
3684
+ SCRIPTS_TEST_FULL = [
3685
+ { name: "test", command: "vitest run", required: true },
3686
+ { name: "test:unit", command: "vitest run --project unit", required: true },
3687
+ { name: "test:integration", command: "vitest run --project integration", required: false },
3688
+ { name: "test:smoke", command: "npm run build && node scripts/smoke.js", required: false }
3689
+ ];
3690
+ SCRIPTS_VERIFY = [
3691
+ { name: "verify", command: "npm run lint && npm run typecheck && npm run test", required: true },
3692
+ { name: "verify:full", command: "npm run lint && npm run typecheck && npm run test && npm run build", required: false }
3693
+ ];
3694
+ PERMISSIONS_BASE = {
3695
+ allow: [
3696
+ "Bash(npm run lint)",
3697
+ "Bash(npm run lint:fix)",
3698
+ "Bash(npm run format)",
3699
+ "Bash(npm run typecheck)",
3700
+ "Bash(npm run test)",
3701
+ "Bash(npm run build)",
3702
+ "Read(**)",
3703
+ "Edit(**)"
3704
+ ],
3705
+ deny: [
3706
+ "Bash(rm -rf /)",
3707
+ "Bash(git push --force)",
3708
+ "Bash(git reset --hard)",
3709
+ "Bash(curl * | sh)",
3710
+ "Bash(npx -y *)"
3711
+ ]
3712
+ };
3713
+ PERMISSIONS_PRODUCTION = {
3714
+ allow: [
3715
+ ...PERMISSIONS_BASE.allow,
3716
+ "Bash(npm run test:unit)",
3717
+ "Bash(npm run test:integration)",
3718
+ "Bash(npm run verify)",
3719
+ "Bash(npm run verify:full)"
3720
+ ],
3721
+ deny: [
3722
+ ...PERMISSIONS_BASE.deny,
3723
+ "Bash(npm publish)",
3724
+ "Bash(git push origin main)"
3725
+ ]
3726
+ };
3727
+ RULES_PROTOTYPE = {
3728
+ specBeforeCode: false,
3729
+ prMergeRequiresReview: false,
3730
+ branchProtection: false,
3731
+ requireWorkflow: false
3732
+ };
3733
+ RULES_PRODUCTION = {
3734
+ specBeforeCode: true,
3735
+ prMergeRequiresReview: true,
3736
+ branchProtection: true,
3737
+ requireWorkflow: true
3738
+ };
3739
+ RULES_TEAM = {
3740
+ specBeforeCode: true,
3741
+ prMergeRequiresReview: true,
3742
+ branchProtection: true,
3743
+ requireWorkflow: true
3744
+ };
3745
+ PRESET_PROTOTYPE = {
3746
+ version: "1.0",
3747
+ scenario: "prototype",
3748
+ preset: "prototype",
3749
+ gates: {
3750
+ preCommit: {
3751
+ enabled: true,
3752
+ scripts: ["format", "lint"]
3753
+ },
3754
+ prePush: {
3755
+ enabled: false,
3756
+ scripts: []
3757
+ },
3758
+ ci: {
3759
+ enabled: true,
3760
+ scripts: ["lint", "build"],
3761
+ triggers: {
3762
+ pullRequest: true,
3763
+ push: { branches: ["main"] }
3764
+ },
3765
+ nodeVersion: "20"
3766
+ }
3767
+ },
3768
+ scripts: [...SCRIPTS_CORE, ...SCRIPTS_TEST_BASIC],
3769
+ permissions: PERMISSIONS_BASE,
3770
+ rules: RULES_PROTOTYPE
3771
+ };
3772
+ PRESET_PRODUCTION = {
3773
+ version: "1.0",
3774
+ scenario: "production",
3775
+ preset: "production",
3776
+ gates: {
3777
+ preCommit: {
3778
+ enabled: true,
3779
+ scripts: ["format", "lint"]
3780
+ },
3781
+ prePush: {
3782
+ enabled: true,
3783
+ scripts: ["typecheck", "test:unit"]
3784
+ },
3785
+ ci: {
3786
+ enabled: true,
3787
+ scripts: ["lint", "typecheck", "test", "build"],
3788
+ triggers: {
3789
+ pullRequest: true,
3790
+ push: { branches: ["main", "develop"] }
3791
+ },
3792
+ nodeVersion: "20"
3793
+ }
3794
+ },
3795
+ scripts: [...SCRIPTS_CORE, ...SCRIPTS_TEST_FULL, ...SCRIPTS_VERIFY],
3796
+ permissions: PERMISSIONS_PRODUCTION,
3797
+ rules: RULES_PRODUCTION
3798
+ };
3799
+ PRESET_TEAM = {
3800
+ version: "1.0",
3801
+ scenario: "team",
3802
+ preset: "team",
3803
+ gates: {
3804
+ preCommit: {
3805
+ enabled: true,
3806
+ scripts: ["format", "lint"]
3807
+ },
3808
+ prePush: {
3809
+ enabled: true,
3810
+ scripts: ["typecheck", "test:unit"]
3811
+ },
3812
+ ci: {
3813
+ enabled: true,
3814
+ scripts: ["lint", "typecheck", "test", "build"],
3815
+ triggers: {
3816
+ pullRequest: true,
3817
+ push: { branches: ["main", "develop"] }
3818
+ },
3819
+ nodeVersion: "20"
3820
+ }
3821
+ },
3822
+ scripts: [...SCRIPTS_CORE, ...SCRIPTS_TEST_FULL, ...SCRIPTS_VERIFY],
3823
+ permissions: PERMISSIONS_PRODUCTION,
3824
+ rules: RULES_TEAM
3825
+ };
3826
+ HARNESS_PRESETS = {
3827
+ prototype: PRESET_PROTOTYPE,
3828
+ production: PRESET_PRODUCTION,
3829
+ team: PRESET_TEAM
3830
+ };
3831
+ }
3832
+ });
3833
+
3834
+ // src/roboco/generators/harness-json.ts
3835
+ import { writeFile } from "fs/promises";
3836
+ import { existsSync } from "fs";
3837
+ import { join as join6 } from "path";
3838
+ async function generateHarnessJson(opts) {
3839
+ const target = join6(opts.cwd, FILENAME);
3840
+ if (existsSync(target) && !opts.force) {
3841
+ return [{ file: FILENAME, action: "skipped", reason: "already exists" }];
3842
+ }
3843
+ const content = JSON.stringify(opts.config, null, 2) + "\n";
3844
+ await writeFile(target, content, "utf8");
3845
+ return [{ file: FILENAME, action: existsSync(target) ? "updated" : "created" }];
3846
+ }
3847
+ var FILENAME;
3848
+ var init_harness_json = __esm({
3849
+ "src/roboco/generators/harness-json.ts"() {
3850
+ "use strict";
3851
+ FILENAME = ".harness.json";
3852
+ }
3853
+ });
3854
+
3855
+ // src/roboco/generators/scripts.ts
3856
+ import { readFile, writeFile as writeFile2 } from "fs/promises";
3857
+ import { existsSync as existsSync2 } from "fs";
3858
+ import { join as join7 } from "path";
3859
+ async function generateScripts(opts) {
3860
+ const pkgPath = join7(opts.cwd, "package.json");
3861
+ if (!existsSync2(pkgPath)) {
3862
+ return [{ file: "package.json", action: "skipped", reason: "not found" }];
3863
+ }
3864
+ const raw = await readFile(pkgPath, "utf8");
3865
+ const pkg5 = JSON.parse(raw);
3866
+ const existing = pkg5.scripts ?? {};
3867
+ const added = [];
3868
+ for (const req of opts.config.scripts) {
3869
+ if (existing[req.name]) continue;
3870
+ if (!req.required && !opts.force) continue;
3871
+ existing[req.name] = req.command;
3872
+ added.push(req.name);
3873
+ }
3874
+ if (added.length === 0) {
3875
+ return [{ file: "package.json", action: "skipped", reason: "all scripts present" }];
3876
+ }
3877
+ pkg5.scripts = existing;
3878
+ const content = JSON.stringify(pkg5, null, 2) + "\n";
3879
+ await writeFile2(pkgPath, content, "utf8");
3880
+ return [{ file: "package.json", action: "updated", reason: `added: ${added.join(", ")}` }];
3881
+ }
3882
+ var init_scripts = __esm({
3883
+ "src/roboco/generators/scripts.ts"() {
3884
+ "use strict";
3885
+ }
3886
+ });
3887
+
3888
+ // src/roboco/generators/lint-staged.ts
3889
+ import { writeFile as writeFile3 } from "fs/promises";
3890
+ import { existsSync as existsSync3 } from "fs";
3891
+ import { join as join8 } from "path";
3892
+ async function generateLintStaged(opts) {
3893
+ const target = join8(opts.cwd, FILENAME2);
3894
+ if (existsSync3(target) && !opts.force) {
3895
+ return [{ file: FILENAME2, action: "skipped", reason: "already exists" }];
3896
+ }
3897
+ const scripts = opts.config.gates.preCommit.scripts;
3898
+ const hasFormat = scripts.includes("format");
3899
+ const hasLint = scripts.includes("lint");
3900
+ const config = {};
3901
+ if (hasFormat) {
3902
+ config["*.{js,ts,tsx,json,md}"] = ["prettier --write"];
3903
+ }
3904
+ if (hasLint) {
3905
+ config["*.{js,ts,tsx}"] = [
3906
+ ...config["*.{js,ts,tsx}"] ?? [],
3907
+ "eslint --fix"
3908
+ ];
3909
+ }
3910
+ if (Object.keys(config).length === 0) {
3911
+ return [{ file: FILENAME2, action: "skipped", reason: "no preCommit scripts mapped" }];
3912
+ }
3913
+ const content = JSON.stringify(config, null, 2) + "\n";
3914
+ await writeFile3(target, content, "utf8");
3915
+ return [{ file: FILENAME2, action: "created" }];
3916
+ }
3917
+ var FILENAME2;
3918
+ var init_lint_staged = __esm({
3919
+ "src/roboco/generators/lint-staged.ts"() {
3920
+ "use strict";
3921
+ FILENAME2 = ".lintstagedrc.json";
3922
+ }
3923
+ });
3924
+
3925
+ // src/roboco/generators/husky.ts
3926
+ import { writeFile as writeFile4, mkdir } from "fs/promises";
3927
+ import { existsSync as existsSync4 } from "fs";
3928
+ import { join as join9 } from "path";
3929
+ function buildHookScript(scripts, useLintStaged) {
3930
+ const lines = ["#!/usr/bin/env sh", '. "$(dirname -- "$0")/_/husky.sh"', ""];
3931
+ if (useLintStaged) {
3932
+ lines.push("npx lint-staged");
3933
+ } else {
3934
+ for (const s of scripts) {
3935
+ lines.push(`npm run ${s}`);
3936
+ }
3937
+ }
3938
+ return lines.join("\n") + "\n";
3939
+ }
3940
+ async function generateHusky(opts) {
3941
+ const results = [];
3942
+ const huskyDir = join9(opts.cwd, HUSKY_DIR);
3943
+ await mkdir(huskyDir, { recursive: true });
3944
+ const preCommit = opts.config.gates.preCommit;
3945
+ if (preCommit.enabled) {
3946
+ const file = join9(huskyDir, "pre-commit");
3947
+ const relPath = `${HUSKY_DIR}/pre-commit`;
3948
+ if (existsSync4(file) && !opts.force) {
3949
+ results.push({ file: relPath, action: "skipped", reason: "already exists" });
3950
+ } else {
3951
+ const content = buildHookScript(preCommit.scripts, true);
3952
+ await writeFile4(file, content, { mode: 493 });
3953
+ results.push({ file: relPath, action: "created" });
3954
+ }
3955
+ }
3956
+ const prePush = opts.config.gates.prePush;
3957
+ if (prePush.enabled) {
3958
+ const file = join9(huskyDir, "pre-push");
3959
+ const relPath = `${HUSKY_DIR}/pre-push`;
3960
+ if (existsSync4(file) && !opts.force) {
3961
+ results.push({ file: relPath, action: "skipped", reason: "already exists" });
3962
+ } else {
3963
+ const content = buildHookScript(prePush.scripts, false);
3964
+ await writeFile4(file, content, { mode: 493 });
3965
+ results.push({ file: relPath, action: "created" });
3966
+ }
3967
+ }
3968
+ return results;
3969
+ }
3970
+ var HUSKY_DIR;
3971
+ var init_husky = __esm({
3972
+ "src/roboco/generators/husky.ts"() {
3973
+ "use strict";
3974
+ HUSKY_DIR = ".husky";
3975
+ }
3976
+ });
3977
+
3978
+ // src/roboco/generators/ci-workflow.ts
3979
+ import { writeFile as writeFile5, mkdir as mkdir2 } from "fs/promises";
3980
+ import { existsSync as existsSync5 } from "fs";
3981
+ import { join as join10 } from "path";
3982
+ function buildWorkflow(opts) {
3983
+ const ci = opts.config.gates.ci;
3984
+ const triggers = [];
3985
+ if (ci.triggers.pullRequest) {
3986
+ triggers.push(" pull_request:");
3987
+ }
3988
+ if (ci.triggers.push.branches.length > 0) {
3989
+ triggers.push(" push:");
3990
+ triggers.push(` branches: [${ci.triggers.push.branches.join(", ")}]`);
3991
+ }
3992
+ const steps = [
3993
+ " - uses: actions/checkout@v4",
3994
+ " - uses: actions/setup-node@v4",
3995
+ " with:",
3996
+ ` node-version: ${ci.nodeVersion}`,
3997
+ " - run: npm ci"
3998
+ ];
3999
+ for (const script of ci.scripts) {
4000
+ steps.push(` - run: npm run ${script}`);
4001
+ }
4002
+ return [
4003
+ "name: CI",
4004
+ "",
4005
+ "on:",
4006
+ ...triggers,
4007
+ "",
4008
+ "jobs:",
4009
+ " verify:",
4010
+ " runs-on: ubuntu-latest",
4011
+ " steps:",
4012
+ ...steps,
4013
+ ""
4014
+ ].join("\n");
4015
+ }
4016
+ async function generateCIWorkflow(opts) {
4017
+ if (!opts.config.gates.ci.enabled) {
4018
+ return [{ file: REL_PATH, action: "skipped", reason: "CI gate disabled" }];
4019
+ }
4020
+ const target = join10(opts.cwd, REL_PATH);
4021
+ if (existsSync5(target) && !opts.force) {
4022
+ return [{ file: REL_PATH, action: "skipped", reason: "already exists" }];
4023
+ }
4024
+ await mkdir2(join10(opts.cwd, ".github", "workflows"), { recursive: true });
4025
+ const content = buildWorkflow(opts);
4026
+ await writeFile5(target, content, "utf8");
4027
+ return [{ file: REL_PATH, action: "created" }];
4028
+ }
4029
+ var REL_PATH;
4030
+ var init_ci_workflow = __esm({
4031
+ "src/roboco/generators/ci-workflow.ts"() {
4032
+ "use strict";
4033
+ REL_PATH = ".github/workflows/ci.yml";
4034
+ }
4035
+ });
4036
+
4037
+ // src/roboco/generators/claude-settings.ts
4038
+ import { readFile as readFile2, writeFile as writeFile6, mkdir as mkdir3 } from "fs/promises";
4039
+ import { existsSync as existsSync6 } from "fs";
4040
+ import { join as join11 } from "path";
4041
+ function dedup(arr) {
4042
+ return [...new Set(arr)];
4043
+ }
4044
+ async function generateClaudeSettings2(opts) {
4045
+ const target = join11(opts.cwd, REL_PATH2);
4046
+ await mkdir3(join11(opts.cwd, ".claude"), { recursive: true });
4047
+ let settings = {};
4048
+ if (existsSync6(target)) {
4049
+ try {
4050
+ const raw = await readFile2(target, "utf8");
4051
+ settings = JSON.parse(raw.replace(/^\uFEFF/, ""));
4052
+ } catch {
4053
+ }
4054
+ }
4055
+ const existing = settings.permissions ?? {};
4056
+ const merged = {
4057
+ allow: dedup([...existing.allow ?? [], ...opts.config.permissions.allow]),
4058
+ deny: dedup([...existing.deny ?? [], ...opts.config.permissions.deny])
4059
+ };
4060
+ settings.permissions = merged;
4061
+ const content = JSON.stringify(settings, null, 2) + "\n";
4062
+ await writeFile6(target, content, "utf8");
4063
+ return [{ file: REL_PATH2, action: existsSync6(target) ? "updated" : "created" }];
4064
+ }
4065
+ var REL_PATH2;
4066
+ var init_claude_settings = __esm({
4067
+ "src/roboco/generators/claude-settings.ts"() {
4068
+ "use strict";
4069
+ REL_PATH2 = ".claude/settings.json";
4070
+ }
4071
+ });
4072
+
4073
+ // src/roboco/generators/claude-md.ts
4074
+ import { readFile as readFile3, writeFile as writeFile7 } from "fs/promises";
4075
+ import { existsSync as existsSync7 } from "fs";
4076
+ import { join as join12 } from "path";
4077
+ function buildRulesBlock(opts) {
4078
+ const { rules, scenario } = opts.config;
4079
+ const lines = [
4080
+ MARKER_START,
4081
+ `## Harness Rules (${scenario})`,
4082
+ ""
4083
+ ];
4084
+ if (rules.specBeforeCode) {
4085
+ lines.push("- spec \uC5C6\uB294 \uAE30\uB2A5 \uAD6C\uD604 \uAE08\uC9C0 \u2014 \uBC18\uB4DC\uC2DC OpenSpec \uBA3C\uC800 \uC791\uC131");
4086
+ }
4087
+ if (rules.requireWorkflow) {
4088
+ lines.push("- \uD070 \uBCC0\uACBD\uC740 plan \u2192 implement \u2192 review \u2192 verify \uC21C\uC11C\uB85C \uC9C4\uD589");
4089
+ }
4090
+ if (rules.prMergeRequiresReview) {
4091
+ lines.push("- PR \uBA38\uC9C0 \uC804 \uB9AC\uBDF0 \uD544\uC218");
4092
+ }
4093
+ if (rules.branchProtection) {
4094
+ lines.push("- main/develop \uBE0C\uB79C\uCE58 \uC9C1\uC811 push \uAE08\uC9C0");
4095
+ }
4096
+ lines.push("- pre-commit/pre-push/CI\uB97C \uD1B5\uACFC\uD558\uC9C0 \uBABB\uD558\uBA74 \uC644\uB8CC\uB85C \uBCF4\uC9C0 \uC54A\uC74C");
4097
+ lines.push("- \uAE30\uC874 script \uC678 \uC784\uC758 \uBA85\uB839 \uCD5C\uC18C\uD654 \u2014 package.json scripts\uB97C \uC0AC\uC6A9");
4098
+ lines.push("");
4099
+ lines.push(MARKER_END);
4100
+ return lines.join("\n");
4101
+ }
4102
+ async function generateClaudeMd2(opts) {
4103
+ const target = join12(opts.cwd, FILENAME3);
4104
+ const block = buildRulesBlock(opts);
4105
+ if (!existsSync7(target)) {
4106
+ const content2 = `# Project Rules
4107
+
4108
+ ${block}
4109
+ `;
4110
+ await writeFile7(target, content2, "utf8");
4111
+ return [{ file: FILENAME3, action: "created" }];
4112
+ }
4113
+ let content = await readFile3(target, "utf8");
4114
+ const startIdx = content.indexOf(MARKER_START);
4115
+ const endIdx = content.indexOf(MARKER_END);
4116
+ if (startIdx !== -1 && endIdx !== -1) {
4117
+ content = content.slice(0, startIdx) + block + content.slice(endIdx + MARKER_END.length);
4118
+ } else {
4119
+ content = content.trimEnd() + "\n\n" + block + "\n";
4120
+ }
4121
+ await writeFile7(target, content, "utf8");
4122
+ return [{ file: FILENAME3, action: "updated" }];
4123
+ }
4124
+ var FILENAME3, MARKER_START, MARKER_END;
4125
+ var init_claude_md = __esm({
4126
+ "src/roboco/generators/claude-md.ts"() {
4127
+ "use strict";
4128
+ FILENAME3 = "CLAUDE.md";
4129
+ MARKER_START = "<!-- harness:start -->";
4130
+ MARKER_END = "<!-- harness:end -->";
4131
+ }
4132
+ });
4133
+
4134
+ // src/roboco/generators/index.ts
4135
+ var init_generators = __esm({
4136
+ "src/roboco/generators/index.ts"() {
4137
+ "use strict";
4138
+ init_harness_json();
4139
+ init_scripts();
4140
+ init_lint_staged();
4141
+ init_husky();
4142
+ init_ci_workflow();
4143
+ init_claude_settings();
4144
+ init_claude_md();
4145
+ }
4146
+ });
4147
+
4148
+ // src/roboco/scaffold.ts
4149
+ async function scaffold(opts) {
4150
+ const scenario = opts.scenario ?? "prototype";
4151
+ const config = resolveConfig({ scenario, overrides: opts.overrides });
4152
+ const generatorOpts = {
4153
+ cwd: opts.cwd,
4154
+ config,
4155
+ force: opts.force
4156
+ };
4157
+ const files = [];
4158
+ for (const gen of GENERATORS) {
4159
+ const results = await gen(generatorOpts);
4160
+ files.push(...results);
4161
+ }
4162
+ const summary = {
4163
+ created: files.filter((f) => f.action === "created").length,
4164
+ updated: files.filter((f) => f.action === "updated").length,
4165
+ skipped: files.filter((f) => f.action === "skipped").length
4166
+ };
4167
+ return { scenario, config, files, summary };
4168
+ }
4169
+ var GENERATORS;
4170
+ var init_scaffold = __esm({
4171
+ "src/roboco/scaffold.ts"() {
4172
+ "use strict";
4173
+ init_harness_defaults();
4174
+ init_generators();
4175
+ GENERATORS = [
4176
+ generateHarnessJson,
4177
+ generateScripts,
4178
+ generateLintStaged,
4179
+ generateHusky,
4180
+ generateCIWorkflow,
4181
+ generateClaudeSettings2,
4182
+ generateClaudeMd2
4183
+ ];
4184
+ }
4185
+ });
4186
+
3608
4187
  // src/core/roboco.ts
3609
4188
  import os2 from "os";
3610
4189
  import path5 from "path";
@@ -4144,7 +4723,7 @@ var doctor_exports = {};
4144
4723
  __export(doctor_exports, {
4145
4724
  runDoctor: () => runDoctor
4146
4725
  });
4147
- import { join as join6 } from "path";
4726
+ import { join as join13 } from "path";
4148
4727
  import { homedir } from "os";
4149
4728
  import fs12 from "fs-extra";
4150
4729
  async function runDoctor() {
@@ -4165,7 +4744,7 @@ async function runDoctor() {
4165
4744
  detail: claudeCheck.detail,
4166
4745
  fix: claudeCheck.ok ? void 0 : "npm install -g @anthropic-ai/claude-code"
4167
4746
  });
4168
- const globalConfigPath = join6(homedir(), ".pai", "config.json");
4747
+ const globalConfigPath = join13(homedir(), ".pai", "config.json");
4169
4748
  const hasGlobalConfig = await fs12.pathExists(globalConfigPath);
4170
4749
  checks.push({
4171
4750
  label: "\uAE00\uB85C\uBC8C \uC124\uC815",
@@ -4543,6 +5122,16 @@ var init_fetch_cmd = __esm({
4543
5122
  });
4544
5123
 
4545
5124
  // src/stages/environment/index.ts
5125
+ function modeToScenario(mode) {
5126
+ switch (mode) {
5127
+ case "prototype":
5128
+ return "prototype";
5129
+ case "poc":
5130
+ return "production";
5131
+ case "production":
5132
+ return "production";
5133
+ }
5134
+ }
4546
5135
  var environmentStage;
4547
5136
  var init_environment = __esm({
4548
5137
  "src/stages/environment/index.ts"() {
@@ -4555,6 +5144,7 @@ var init_environment = __esm({
4555
5144
  init_claude_commands();
4556
5145
  init_detector();
4557
5146
  init_config();
5147
+ init_scaffold();
4558
5148
  init_progress();
4559
5149
  init_ui();
4560
5150
  init_analyzer();
@@ -4673,6 +5263,21 @@ var init_environment = __esm({
4673
5263
  }
4674
5264
  }
4675
5265
  console.log("");
5266
+ const scaffoldResult = await withSpinner("Harness \uC815\uCC45 \uC124\uC815 \uC911...", async () => {
5267
+ const result = await scaffold({
5268
+ cwd: input.cwd,
5269
+ scenario: modeToScenario(interview.mode)
5270
+ });
5271
+ await sleep(300);
5272
+ return result;
5273
+ });
5274
+ if (scaffoldResult.summary.created > 0 || scaffoldResult.summary.updated > 0) {
5275
+ const parts = [];
5276
+ if (scaffoldResult.summary.created > 0) parts.push(`${scaffoldResult.summary.created}\uAC1C \uC0DD\uC131`);
5277
+ if (scaffoldResult.summary.updated > 0) parts.push(`${scaffoldResult.summary.updated}\uAC1C \uC218\uC815`);
5278
+ success(`Harness: ${parts.join(", ")}`);
5279
+ }
5280
+ console.log("");
4676
5281
  await withSpinner("\uC124\uC815 \uC800\uC7A5 \uC911...", async () => {
4677
5282
  const config = createDefaultConfig(interview.projectName, interview.mode);
4678
5283
  config.plugins = pluginKeys;
@@ -4707,11 +5312,11 @@ var init_environment = __esm({
4707
5312
  });
4708
5313
 
4709
5314
  // src/stages/validation/runner.ts
4710
- import { join as join7 } from "path";
5315
+ import { join as join14 } from "path";
4711
5316
  import fs15 from "fs-extra";
4712
5317
  async function runTests(cwd) {
4713
5318
  const start = Date.now();
4714
- const gstackPath = join7(cwd, ".pai", "gstack.json");
5319
+ const gstackPath = join14(cwd, ".pai", "gstack.json");
4715
5320
  let runner = "npm test";
4716
5321
  if (await fs15.pathExists(gstackPath)) {
4717
5322
  try {
@@ -4722,7 +5327,7 @@ async function runTests(cwd) {
4722
5327
  } catch {
4723
5328
  }
4724
5329
  }
4725
- const pkgPath = join7(cwd, "package.json");
5330
+ const pkgPath = join14(cwd, "package.json");
4726
5331
  if (await fs15.pathExists(pkgPath)) {
4727
5332
  try {
4728
5333
  const pkg5 = await fs15.readJson(pkgPath);
@@ -4767,10 +5372,10 @@ var init_runner = __esm({
4767
5372
  });
4768
5373
 
4769
5374
  // src/stages/validation/harness.ts
4770
- import { join as join8 } from "path";
5375
+ import { join as join15 } from "path";
4771
5376
  import fs16 from "fs-extra";
4772
5377
  async function runHarnessCheck(cwd) {
4773
- const harnessPath = join8(cwd, ".pai", "harness.json");
5378
+ const harnessPath = join15(cwd, ".pai", "harness.json");
4774
5379
  if (!await fs16.pathExists(harnessPath)) {
4775
5380
  return { enabled: false, specFile: null, rules: [], checks: [] };
4776
5381
  }
@@ -4784,8 +5389,8 @@ async function runHarnessCheck(cwd) {
4784
5389
  const rules = config.rules ?? [];
4785
5390
  const checks = [];
4786
5391
  if (rules.includes("spec-implementation-match")) {
4787
- const specExists = await fs16.pathExists(join8(cwd, specFile));
4788
- const srcExists = await fs16.pathExists(join8(cwd, "src"));
5392
+ const specExists = await fs16.pathExists(join15(cwd, specFile));
5393
+ const srcExists = await fs16.pathExists(join15(cwd, "src"));
4789
5394
  checks.push({
4790
5395
  rule: "spec-implementation-match",
4791
5396
  passed: specExists && srcExists,
@@ -4793,8 +5398,8 @@ async function runHarnessCheck(cwd) {
4793
5398
  });
4794
5399
  }
4795
5400
  if (rules.includes("api-contract-test")) {
4796
- const testDir = await fs16.pathExists(join8(cwd, "tests"));
4797
- const testDir2 = await fs16.pathExists(join8(cwd, "test"));
5401
+ const testDir = await fs16.pathExists(join15(cwd, "tests"));
5402
+ const testDir2 = await fs16.pathExists(join15(cwd, "test"));
4798
5403
  checks.push({
4799
5404
  rule: "api-contract-test",
4800
5405
  passed: testDir || testDir2,
@@ -4951,12 +5556,189 @@ var init_analyze = __esm({
4951
5556
  }
4952
5557
  });
4953
5558
 
5559
+ // src/vibe-ready/evaluator.ts
5560
+ var evaluator_exports = {};
5561
+ __export(evaluator_exports, {
5562
+ evaluateHarness: () => evaluateHarness
5563
+ });
5564
+ import { readFile as readFile4 } from "fs/promises";
5565
+ import { existsSync as existsSync8 } from "fs";
5566
+ import { join as join16 } from "path";
5567
+ async function evaluateHarness(cwd) {
5568
+ const configPath = join16(cwd, ".harness.json");
5569
+ if (!existsSync8(configPath)) {
5570
+ return {
5571
+ configFound: false,
5572
+ scenario: null,
5573
+ gates: [],
5574
+ missingScripts: [],
5575
+ missingFiles: [],
5576
+ score: 0
5577
+ };
5578
+ }
5579
+ let config;
5580
+ try {
5581
+ const raw = await readFile4(configPath, "utf8");
5582
+ config = JSON.parse(raw);
5583
+ } catch {
5584
+ return {
5585
+ configFound: false,
5586
+ scenario: null,
5587
+ gates: [],
5588
+ missingScripts: [],
5589
+ missingFiles: [],
5590
+ score: 0
5591
+ };
5592
+ }
5593
+ const gates = [];
5594
+ const missingScripts = [];
5595
+ const missingFiles = [];
5596
+ let score = 10;
5597
+ gates.push(await evaluateGate(cwd, config, "preCommit", [".husky/pre-commit", ".lintstagedrc.json"]));
5598
+ if (gates[gates.length - 1].status === "pass") score += 15;
5599
+ else missingFiles.push(...gates[gates.length - 1].findings.filter((f) => f.startsWith("missing:")).map((f) => f.slice(8)));
5600
+ gates.push(await evaluateGate(cwd, config, "prePush", [".husky/pre-push"]));
5601
+ if (gates[gates.length - 1].status === "pass") score += 15;
5602
+ else if (gates[gates.length - 1].status === "skip") score += 15;
5603
+ gates.push(await evaluateGate(cwd, config, "ci", [".github/workflows/ci.yml"]));
5604
+ if (gates[gates.length - 1].status === "pass") score += 20;
5605
+ const scriptScore = await evaluateScripts(cwd, config, missingScripts);
5606
+ score += scriptScore;
5607
+ const permScore = await evaluatePermissions(cwd, config);
5608
+ score += permScore;
5609
+ const rulesScore = await evaluateRules(cwd, config);
5610
+ score += rulesScore;
5611
+ return {
5612
+ configFound: true,
5613
+ scenario: config.scenario,
5614
+ gates,
5615
+ missingScripts,
5616
+ missingFiles,
5617
+ score: Math.min(100, score)
5618
+ };
5619
+ }
5620
+ async function evaluateGate(cwd, config, gateName, expectedFiles) {
5621
+ const gate = config.gates[gateName];
5622
+ if (!gate.enabled) {
5623
+ return { gate: gateName, status: "skip", findings: ["gate disabled"] };
5624
+ }
5625
+ const findings = [];
5626
+ let allPresent = true;
5627
+ for (const file of expectedFiles) {
5628
+ if (!existsSync8(join16(cwd, file))) {
5629
+ findings.push(`missing:${file}`);
5630
+ allPresent = false;
5631
+ }
5632
+ }
5633
+ const pkg5 = await readPackageJson(cwd);
5634
+ if (pkg5) {
5635
+ for (const script of gate.scripts) {
5636
+ if (!pkg5.scripts?.[script]) {
5637
+ findings.push(`missing script: ${script}`);
5638
+ allPresent = false;
5639
+ }
5640
+ }
5641
+ }
5642
+ return {
5643
+ gate: gateName,
5644
+ status: allPresent ? "pass" : "fail",
5645
+ findings
5646
+ };
5647
+ }
5648
+ async function evaluateScripts(cwd, config, missingScripts) {
5649
+ const pkg5 = await readPackageJson(cwd);
5650
+ if (!pkg5) return 0;
5651
+ const required = config.scripts.filter((s) => s.required);
5652
+ if (required.length === 0) return 20;
5653
+ let found = 0;
5654
+ for (const req of required) {
5655
+ if (pkg5.scripts?.[req.name]) {
5656
+ found++;
5657
+ } else {
5658
+ missingScripts.push(req.name);
5659
+ }
5660
+ }
5661
+ return Math.round(found / required.length * 20);
5662
+ }
5663
+ async function evaluatePermissions(cwd, config) {
5664
+ const settingsPath = join16(cwd, ".claude", "settings.json");
5665
+ if (!existsSync8(settingsPath)) return 0;
5666
+ try {
5667
+ const raw = await readFile4(settingsPath, "utf8");
5668
+ const settings = JSON.parse(raw.replace(/^\uFEFF/, ""));
5669
+ const perms = settings.permissions;
5670
+ if (!perms) return 0;
5671
+ let matchCount = 0;
5672
+ let totalChecks = 0;
5673
+ for (const deny of config.permissions.deny) {
5674
+ totalChecks++;
5675
+ if (perms.deny?.includes(deny)) matchCount++;
5676
+ }
5677
+ for (const allow of config.permissions.allow) {
5678
+ totalChecks++;
5679
+ if (perms.allow?.includes(allow)) matchCount++;
5680
+ }
5681
+ if (totalChecks === 0) return 10;
5682
+ return Math.round(matchCount / totalChecks * 10);
5683
+ } catch {
5684
+ return 0;
5685
+ }
5686
+ }
5687
+ async function evaluateRules(cwd, config) {
5688
+ const claudeMdPath = join16(cwd, "CLAUDE.md");
5689
+ if (!existsSync8(claudeMdPath)) return 0;
5690
+ try {
5691
+ const content = await readFile4(claudeMdPath, "utf8");
5692
+ let matchCount = 0;
5693
+ let totalRules = 0;
5694
+ if (config.rules.specBeforeCode) {
5695
+ totalRules++;
5696
+ if (content.includes("spec") && content.includes("\uAE08\uC9C0")) matchCount++;
5697
+ }
5698
+ if (config.rules.requireWorkflow) {
5699
+ totalRules++;
5700
+ if (content.includes("plan") && content.includes("review")) matchCount++;
5701
+ }
5702
+ if (config.rules.prMergeRequiresReview) {
5703
+ totalRules++;
5704
+ if (content.includes("\uB9AC\uBDF0") || content.includes("review")) matchCount++;
5705
+ }
5706
+ if (config.rules.branchProtection) {
5707
+ totalRules++;
5708
+ if (content.includes("main") || content.includes("\uBE0C\uB79C\uCE58")) matchCount++;
5709
+ }
5710
+ if (content.includes("<!-- harness:start -->")) {
5711
+ matchCount++;
5712
+ totalRules++;
5713
+ }
5714
+ if (totalRules === 0) return 10;
5715
+ return Math.round(matchCount / totalRules * 10);
5716
+ } catch {
5717
+ return 0;
5718
+ }
5719
+ }
5720
+ async function readPackageJson(cwd) {
5721
+ const pkgPath = join16(cwd, "package.json");
5722
+ if (!existsSync8(pkgPath)) return null;
5723
+ try {
5724
+ const raw = await readFile4(pkgPath, "utf8");
5725
+ return JSON.parse(raw);
5726
+ } catch {
5727
+ return null;
5728
+ }
5729
+ }
5730
+ var init_evaluator = __esm({
5731
+ "src/vibe-ready/evaluator.ts"() {
5732
+ "use strict";
5733
+ }
5734
+ });
5735
+
4954
5736
  // src/stages/evaluation/analyzer.ts
4955
5737
  var analyzer_exports2 = {};
4956
5738
  __export(analyzer_exports2, {
4957
5739
  analyzeRepository: () => analyzeRepository
4958
5740
  });
4959
- import { join as join9 } from "path";
5741
+ import { join as join17 } from "path";
4960
5742
  import fs17 from "fs-extra";
4961
5743
  async function analyzeRepository(repoPath) {
4962
5744
  try {
@@ -5018,14 +5800,14 @@ async function checkTestCoverage(repoPath) {
5018
5800
  ".nycrc"
5019
5801
  ];
5020
5802
  for (const f of testConfigs) {
5021
- const found = await fs17.pathExists(join9(repoPath, f));
5803
+ const found = await fs17.pathExists(join17(repoPath, f));
5022
5804
  findings.push({ item: f, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
5023
5805
  if (found) score += 20;
5024
5806
  }
5025
5807
  const testDirs = ["tests", "test", "__tests__", "spec"];
5026
5808
  let hasTestDir = false;
5027
5809
  for (const d of testDirs) {
5028
- if (await fs17.pathExists(join9(repoPath, d))) {
5810
+ if (await fs17.pathExists(join17(repoPath, d))) {
5029
5811
  findings.push({ item: d, found: true, details: "\uD14C\uC2A4\uD2B8 \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC" });
5030
5812
  hasTestDir = true;
5031
5813
  score += 30;
@@ -5049,7 +5831,7 @@ async function checkCiCd(repoPath) {
5049
5831
  { path: ".circleci", label: "CircleCI" }
5050
5832
  ];
5051
5833
  for (const { path: path10, label } of ciConfigs) {
5052
- const found = await fs17.pathExists(join9(repoPath, path10));
5834
+ const found = await fs17.pathExists(join17(repoPath, path10));
5053
5835
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
5054
5836
  if (found) score += 40;
5055
5837
  }
@@ -5068,7 +5850,7 @@ async function checkHooks(repoPath) {
5068
5850
  { path: ".claude/settings.json", label: "Claude Code settings" }
5069
5851
  ];
5070
5852
  for (const { path: path10, label } of hookConfigs) {
5071
- const found = await fs17.pathExists(join9(repoPath, path10));
5853
+ const found = await fs17.pathExists(join17(repoPath, path10));
5072
5854
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
5073
5855
  if (found) score += 20;
5074
5856
  }
@@ -5086,7 +5868,7 @@ async function checkRepoStructure(repoPath) {
5086
5868
  { path: ".gitignore", label: ".gitignore" }
5087
5869
  ];
5088
5870
  for (const { path: path10, label } of structureChecks) {
5089
- const found = await fs17.pathExists(join9(repoPath, path10));
5871
+ const found = await fs17.pathExists(join17(repoPath, path10));
5090
5872
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
5091
5873
  if (found) score += 25;
5092
5874
  }
@@ -5103,7 +5885,7 @@ async function checkDocumentation(repoPath) {
5103
5885
  { path: "docs/openspec.md", label: "OpenSpec PRD", points: 25 }
5104
5886
  ];
5105
5887
  for (const { path: path10, label, points } of docChecks) {
5106
- const found = await fs17.pathExists(join9(repoPath, path10));
5888
+ const found = await fs17.pathExists(join17(repoPath, path10));
5107
5889
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
5108
5890
  if (found) score += points;
5109
5891
  }
@@ -5111,6 +5893,54 @@ async function checkDocumentation(repoPath) {
5111
5893
  return { name: "\uBB38\uC11C\uD654 \uC218\uC900", tier: "nice", score, recommendations: [], rawFindings: findings };
5112
5894
  }
5113
5895
  async function checkHarnessEngineering(repoPath) {
5896
+ if (await fs17.pathExists(join17(repoPath, ".harness.json"))) {
5897
+ return checkHarnessWithPolicy(repoPath);
5898
+ }
5899
+ return checkHarnessLegacy(repoPath);
5900
+ }
5901
+ async function checkHarnessWithPolicy(repoPath) {
5902
+ const { evaluateHarness: evaluateHarness2 } = await Promise.resolve().then(() => (init_evaluator(), evaluator_exports));
5903
+ const evaluation = await evaluateHarness2(repoPath);
5904
+ const findings = [];
5905
+ findings.push({
5906
+ item: ".harness.json",
5907
+ found: evaluation.configFound,
5908
+ details: evaluation.configFound ? `\uC2DC\uB098\uB9AC\uC624: ${evaluation.scenario}` : "\uC5C6\uC74C"
5909
+ });
5910
+ for (const gate of evaluation.gates) {
5911
+ findings.push({
5912
+ item: `gate:${gate.gate}`,
5913
+ found: gate.status === "pass" || gate.status === "skip",
5914
+ details: gate.status === "pass" ? "\uD1B5\uACFC" : gate.status === "skip" ? "\uBE44\uD65C\uC131 (OK)" : gate.findings.join(", ")
5915
+ });
5916
+ }
5917
+ for (const script of evaluation.missingScripts) {
5918
+ findings.push({ item: `script:${script}`, found: false, details: "\uB204\uB77D" });
5919
+ }
5920
+ const recommendations = [];
5921
+ if (evaluation.missingScripts.length > 0) {
5922
+ recommendations.push({
5923
+ severity: "warning",
5924
+ message: `\uD544\uC218 script ${evaluation.missingScripts.length}\uAC1C \uB204\uB77D`,
5925
+ action: `package.json\uC5D0 \uCD94\uAC00: ${evaluation.missingScripts.join(", ")}`
5926
+ });
5927
+ }
5928
+ if (evaluation.missingFiles.length > 0) {
5929
+ recommendations.push({
5930
+ severity: "warning",
5931
+ message: `Harness \uD30C\uC77C ${evaluation.missingFiles.length}\uAC1C \uB204\uB77D`,
5932
+ action: `roboco scaffold \uC2E4\uD589 \uB610\uB294 \uC218\uB3D9 \uC0DD\uC131: ${evaluation.missingFiles.join(", ")}`
5933
+ });
5934
+ }
5935
+ return {
5936
+ name: "\uD558\uB124\uC2A4 \uC5D4\uC9C0\uB2C8\uC5B4\uB9C1",
5937
+ tier: "nice",
5938
+ score: evaluation.score,
5939
+ recommendations,
5940
+ rawFindings: findings
5941
+ };
5942
+ }
5943
+ async function checkHarnessLegacy(repoPath) {
5114
5944
  const findings = [];
5115
5945
  let score = 0;
5116
5946
  const harnessChecks = [
@@ -5122,12 +5952,20 @@ async function checkHarnessEngineering(repoPath) {
5122
5952
  { path: ".pai/config.json", label: "PAI config", points: 10 }
5123
5953
  ];
5124
5954
  for (const { path: path10, label, points } of harnessChecks) {
5125
- const found = await fs17.pathExists(join9(repoPath, path10));
5955
+ const found = await fs17.pathExists(join17(repoPath, path10));
5126
5956
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
5127
5957
  if (found) score += points;
5128
5958
  }
5129
5959
  score = Math.min(100, score);
5130
- return { name: "\uD558\uB124\uC2A4 \uC5D4\uC9C0\uB2C8\uC5B4\uB9C1", tier: "nice", score, recommendations: [], rawFindings: findings };
5960
+ const recommendations = [];
5961
+ if (!await fs17.pathExists(join17(repoPath, ".harness.json"))) {
5962
+ recommendations.push({
5963
+ severity: "info",
5964
+ message: ".harness.json \uBBF8\uAC10\uC9C0",
5965
+ action: "roboco scaffold\uB97C \uC2E4\uD589\uD558\uBA74 \uC120\uC5B8\uC801 \uC815\uCC45 \uAE30\uBC18 \uD3C9\uAC00\uB85C \uC804\uD658\uB429\uB2C8\uB2E4"
5966
+ });
5967
+ }
5968
+ return { name: "\uD558\uB124\uC2A4 \uC5D4\uC9C0\uB2C8\uC5B4\uB9C1", tier: "nice", score, recommendations, rawFindings: findings };
5131
5969
  }
5132
5970
  var init_analyzer2 = __esm({
5133
5971
  "src/stages/evaluation/analyzer.ts"() {
@@ -5505,7 +6343,7 @@ __export(shell_cd_exports, {
5505
6343
  installShellHelper: () => installShellHelper,
5506
6344
  requestCdAfter: () => requestCdAfter
5507
6345
  });
5508
- import { join as join10 } from "path";
6346
+ import { join as join18 } from "path";
5509
6347
  import { homedir as homedir2 } from "os";
5510
6348
  import fs18 from "fs-extra";
5511
6349
  async function requestCdAfter(targetDir) {
@@ -5542,7 +6380,7 @@ async function installPowerShellHelper() {
5542
6380
  await fs18.writeFile(HELPER_FILE_PS1, POWERSHELL_HELPER);
5543
6381
  const rcFile = getShellRcPath();
5544
6382
  const sourceLine = '. "$env:USERPROFILE\\.pai\\shell-helper.ps1"';
5545
- await fs18.ensureDir(join10(rcFile, ".."));
6383
+ await fs18.ensureDir(join18(rcFile, ".."));
5546
6384
  if (await fs18.pathExists(rcFile)) {
5547
6385
  const content = await fs18.readFile(rcFile, "utf8");
5548
6386
  if (content.includes("shell-helper.ps1")) {
@@ -5563,10 +6401,10 @@ var init_shell_cd = __esm({
5563
6401
  "src/utils/shell-cd.ts"() {
5564
6402
  "use strict";
5565
6403
  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");
6404
+ PAI_DIR = join18(homedir2(), ".pai");
6405
+ CD_FILE = join18(PAI_DIR, ".cd-after");
6406
+ HELPER_FILE_SH = join18(PAI_DIR, "shell-helper.sh");
6407
+ HELPER_FILE_PS1 = join18(PAI_DIR, "shell-helper.ps1");
5570
6408
  BASH_HELPER = `# PAI shell helper \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9 \uC9C0\uC6D0
5571
6409
  pai() {
5572
6410
  local cd_target="$HOME/.pai/.cd-after"
@@ -5701,7 +6539,7 @@ function mergeOmcIntoSettings(settings, marketplaceId, marketplaceUrl, pluginId)
5701
6539
  return next;
5702
6540
  }
5703
6541
  var DEFAULT_MARKETPLACE_ID, DEFAULT_MARKETPLACE_URL, DEFAULT_PLUGIN_ID, ClaudeSettingsError;
5704
- var init_claude_settings = __esm({
6542
+ var init_claude_settings2 = __esm({
5705
6543
  "src/utils/claude-settings.ts"() {
5706
6544
  "use strict";
5707
6545
  init_platform();
@@ -5720,13 +6558,13 @@ var init_claude_settings = __esm({
5720
6558
  });
5721
6559
 
5722
6560
  // src/stages/evaluation/cache.ts
5723
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
5724
- import { join as join11 } from "path";
6561
+ import { readFileSync, writeFileSync, mkdirSync, existsSync as existsSync9 } from "fs";
6562
+ import { join as join19 } from "path";
5725
6563
  import { createHash } from "crypto";
5726
6564
  function computeRepoHash(repoPath) {
5727
6565
  const hash = createHash("sha256");
5728
6566
  for (const file of FILES_TO_HASH) {
5729
- const fullPath = join11(repoPath, file);
6567
+ const fullPath = join19(repoPath, file);
5730
6568
  try {
5731
6569
  const content = readFileSync(fullPath);
5732
6570
  hash.update(`${file}:${content.length}`);
@@ -5737,7 +6575,7 @@ function computeRepoHash(repoPath) {
5737
6575
  return hash.digest("hex").slice(0, 16);
5738
6576
  }
5739
6577
  function getCachePath(repoPath) {
5740
- return join11(repoPath, CACHE_DIR, CACHE_FILE);
6578
+ return join19(repoPath, CACHE_DIR, CACHE_FILE);
5741
6579
  }
5742
6580
  function loadCache(repoPath) {
5743
6581
  try {
@@ -5748,8 +6586,8 @@ function loadCache(repoPath) {
5748
6586
  }
5749
6587
  }
5750
6588
  function saveCache(repoPath, store) {
5751
- const cacheDir = join11(repoPath, CACHE_DIR, "cache");
5752
- if (!existsSync(cacheDir)) {
6589
+ const cacheDir = join19(repoPath, CACHE_DIR, "cache");
6590
+ if (!existsSync9(cacheDir)) {
5753
6591
  mkdirSync(cacheDir, { recursive: true });
5754
6592
  }
5755
6593
  writeFileSync(getCachePath(repoPath), JSON.stringify(store, null, 2));
@@ -5807,7 +6645,7 @@ var evaluate_cmd_exports = {};
5807
6645
  __export(evaluate_cmd_exports, {
5808
6646
  evaluateCommand: () => evaluateCommand
5809
6647
  });
5810
- import { join as join12, basename } from "path";
6648
+ import { join as join20, basename } from "path";
5811
6649
  import fs20 from "fs-extra";
5812
6650
  async function evaluateCommand(cwd, options) {
5813
6651
  const useCache = options.cache !== false;
@@ -5831,8 +6669,8 @@ async function evaluateCommand(cwd, options) {
5831
6669
  const config = await loadConfig(cwd);
5832
6670
  const projectName = config?.projectName ?? basename(cwd);
5833
6671
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5834
- const reportDir = join12(cwd, "docs", "p-reports");
5835
- const reportPath = join12(reportDir, `${today}.md`);
6672
+ const reportDir = join20(cwd, "docs", "p-reports");
6673
+ const reportPath = join20(reportDir, `${today}.md`);
5836
6674
  await fs20.ensureDir(reportDir);
5837
6675
  const detailedReport = buildDetailedReport(result, projectName);
5838
6676
  await fs20.writeFile(reportPath, detailedReport, "utf8");
@@ -6092,7 +6930,7 @@ var init_cmd_exports = {};
6092
6930
  __export(init_cmd_exports, {
6093
6931
  initCommand: () => initCommand
6094
6932
  });
6095
- import { join as join13, basename as basename2 } from "path";
6933
+ import { join as join21, basename as basename2 } from "path";
6096
6934
  import fs21 from "fs-extra";
6097
6935
  async function initCommand(cwd, nameArg) {
6098
6936
  printWelcomeBanner();
@@ -6151,11 +6989,11 @@ async function initCommand(cwd, nameArg) {
6151
6989
  const evalResult = computeResult2(llmOutput);
6152
6990
  printReport2(evalResult);
6153
6991
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6154
- const reportDir = join13(cwd, "docs", "p-reports");
6992
+ const reportDir = join21(cwd, "docs", "p-reports");
6155
6993
  await fs21.ensureDir(reportDir);
6156
6994
  const legacyName = basename2(cwd);
6157
6995
  const detailedReport = buildDetailedReport3(evalResult, legacyName);
6158
- await fs21.writeFile(join13(reportDir, `${today}.md`), detailedReport, "utf8");
6996
+ await fs21.writeFile(join21(reportDir, `${today}.md`), detailedReport, "utf8");
6159
6997
  console.log("");
6160
6998
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
6161
6999
  } catch {
@@ -6175,7 +7013,7 @@ async function initCommand(cwd, nameArg) {
6175
7013
  return;
6176
7014
  }
6177
7015
  }
6178
- step(1, 3, "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984");
7016
+ step(1, TOTAL_STEPS, "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815");
6179
7017
  hint("\uC774 \uC774\uB984\uC73C\uB85C \uD504\uB85C\uC81D\uD2B8 \uD3F4\uB354\uAC00 \uC0DD\uC131\uB429\uB2C8\uB2E4.");
6180
7018
  hint("\uC601\uBB38 \uC18C\uBB38\uC790, \uC22B\uC790, \uD558\uC774\uD508(-)\uC744 \uC0AC\uC6A9\uD574\uC8FC\uC138\uC694.");
6181
7019
  console.log("");
@@ -6200,7 +7038,7 @@ async function initCommand(cwd, nameArg) {
6200
7038
  }]);
6201
7039
  projectName = answer.name.trim();
6202
7040
  }
6203
- const projectDir = join13(cwd, projectName);
7041
+ const projectDir = join21(cwd, projectName);
6204
7042
  if (await fs21.pathExists(projectDir)) {
6205
7043
  const existingConfig = await loadConfig(projectDir);
6206
7044
  if (existingConfig) {
@@ -6227,7 +7065,6 @@ async function setupInDirectory(projectDir, projectName) {
6227
7065
  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
7066
  console.log("");
6229
7067
  }
6230
- step(2, 3, "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815");
6231
7068
  const config = createDefaultConfig(projectName, "prototype");
6232
7069
  const input = {
6233
7070
  cwd: projectDir,
@@ -6260,55 +7097,55 @@ async function setupInDirectory(projectDir, projectName) {
6260
7097
  }
6261
7098
  const interview = result.data.interview;
6262
7099
  const isCurrentDir = projectDir === process.cwd();
6263
- await showCompletion(projectName, projectDir, interview.extraTools, isCurrentDir);
7100
+ await showCompletion(projectName, projectDir, interview, isCurrentDir);
6264
7101
  } else {
6265
7102
  for (const err of result.errors) {
6266
7103
  error(err.message);
6267
7104
  }
6268
7105
  }
6269
7106
  }
6270
- async function showCompletion(projectName, projectDir, extraTools, isCurrentDir) {
7107
+ async function showCompletion(projectName, projectDir, interview, isCurrentDir) {
6271
7108
  const { default: inquirer } = await import("inquirer");
6272
7109
  const chalk9 = (await import("chalk")).default;
6273
7110
  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!"));
7111
+ const extraTools = interview.extraTools;
7112
+ step(4, TOTAL_STEPS, "\uC124\uCE58 \uD6C4 \uC548\uB0B4");
7113
+ console.log(colors.success(" \uD504\uB85C\uC81D\uD2B8 \uC900\uBE44\uAC00 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4!"));
7114
+ 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
7115
  console.log("");
6277
- console.log(colors.dim(" \uC0DD\uC131\uB41C \uD56D\uBAA9"));
7116
+ console.log(colors.accent(" \uC124\uCE58\uB41C \uAE30\uB2A5"));
6278
7117
  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`);
7118
+ console.log(` ${chalk9.cyan("OpenSpec")} PRD \uC124\uACC4 \uD15C\uD50C\uB9BF`);
7119
+ console.log(` ${chalk9.cyan("RoboCo")} \uD30C\uC774\uD504\uB77C\uC778 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158`);
7120
+ console.log(` ${chalk9.cyan("Harness")} \uD488\uC9C8 \uC815\uCC45 (hook \xB7 CI \xB7 lint)`);
7121
+ console.log(` ${chalk9.cyan("Vibe-Ready")} AI \uAC1C\uBC1C \uC900\uBE44\uB3C4 \uC9C4\uB2E8`);
6289
7122
  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)`);
7123
+ console.log(` ${chalk9.cyan("OMC")} AI \uC5D0\uC774\uC804\uD2B8 \uC791\uC5C5 \uBD84\uC5C5`);
6291
7124
  }
6292
7125
  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`);
7126
+ console.log(` ${chalk9.cyan("gstack")} \uD14C\uC2A4\uD2B8 \uC790\uB3D9\uD654`);
6297
7127
  }
6298
7128
  if (extraTools.includes("vercel")) {
6299
- console.log(` ${chalk9.cyan("Vercel")} \uC790\uB3D9 \uBC30\uD3EC \u2014 Preview + Production`);
7129
+ console.log(` ${chalk9.cyan("Vercel")} \uC790\uB3D9 \uBC30\uD3EC (Preview + Production)`);
6300
7130
  }
6301
7131
  if (extraTools.includes("supabase")) {
6302
- console.log(` ${chalk9.cyan("Supabase")} DB + \uC778\uC99D + \uC2A4\uD1A0\uB9AC\uC9C0 (PostgreSQL \uAE30\uBC18)`);
7132
+ console.log(` ${chalk9.cyan("Supabase")} DB + \uC778\uC99D + \uC2A4\uD1A0\uB9AC\uC9C0`);
7133
+ }
7134
+ if (extraTools.includes("mcp") && interview.mcp) {
7135
+ console.log(` ${chalk9.cyan("MCP \uC11C\uBC84")} ${interview.mcp.name} (${interview.mcp.type})`);
7136
+ }
7137
+ if (interview.authMethods.length > 0) {
7138
+ console.log(` ${chalk9.cyan("\uB85C\uADF8\uC778")} ${interview.authMethods.join(", ")}`);
6303
7139
  }
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`);
7140
+ if (interview.recipes && interview.recipes.length > 0) {
7141
+ console.log(` ${chalk9.cyan("\uC0AC\uB0B4 \uC5F0\uB3D9")} ${interview.recipes.join(", ")}`);
6306
7142
  }
6307
7143
  console.log("");
6308
- console.log(colors.accent(" \uC124\uCE58 \uD655\uC778"));
7144
+ console.log(colors.accent(" \uD30C\uC77C \uD655\uC778"));
6309
7145
  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
7146
  const checks = [
6311
7147
  { label: "AI \uCEE8\uD14D\uC2A4\uD2B8", path: "CLAUDE.md" },
7148
+ { label: "Harness \uC815\uCC45", path: ".harness.json" },
6312
7149
  { label: "PRD \uD15C\uD50C\uB9BF", path: "docs/openspec.md" },
6313
7150
  { label: "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815", path: ".pai/config.json" },
6314
7151
  { label: "Claude \uC124\uC815", path: ".claude/settings.json" },
@@ -6316,10 +7153,42 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
6316
7153
  ...extraTools.includes("omc") ? [{ label: "OMC \uB7F0\uD0C0\uC784", path: ".omc" }] : []
6317
7154
  ];
6318
7155
  for (const check of checks) {
6319
- const exists = await fs21.pathExists(join13(projectDir, check.path));
7156
+ const exists = await fs21.pathExists(join21(projectDir, check.path));
6320
7157
  console.log(` ${exists ? colors.success("\u2713") : colors.err("\u2717")} ${check.label.padEnd(16)} ${colors.dim(check.path)}`);
6321
7158
  }
6322
7159
  console.log("");
7160
+ console.log(colors.accent(" \uB2E4\uC74C \uD560 \uC77C"));
7161
+ 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"));
7162
+ if (interview.authMethods.length > 0) {
7163
+ if (interview.authMethods.includes("custom") && !interview.customAuth?.clientId) {
7164
+ console.log(` ${chalk9.yellow("\u2192")} .env.local \uC5D0 OAuth \uD0A4\uB97C \uCC44\uC6CC\uC8FC\uC138\uC694`);
7165
+ }
7166
+ if (interview.authMethods.includes("google")) {
7167
+ console.log(` ${chalk9.yellow("\u2192")} Google Cloud Console\uC5D0\uC11C OAuth \uD074\uB77C\uC774\uC5B8\uD2B8 \uC0DD\uC131`);
7168
+ }
7169
+ if (interview.authMethods.includes("kakao")) {
7170
+ console.log(` ${chalk9.yellow("\u2192")} Kakao Developers\uC5D0\uC11C \uC571 \uB4F1\uB85D`);
7171
+ }
7172
+ if (interview.authMethods.includes("naver")) {
7173
+ console.log(` ${chalk9.yellow("\u2192")} Naver Developers\uC5D0\uC11C \uC571 \uB4F1\uB85D`);
7174
+ }
7175
+ }
7176
+ if (extraTools.includes("vercel")) {
7177
+ console.log(` ${chalk9.yellow("\u2192")} vercel login \uC73C\uB85C Vercel \uACC4\uC815 \uC5F0\uACB0`);
7178
+ }
7179
+ if (extraTools.includes("supabase")) {
7180
+ console.log(` ${chalk9.yellow("\u2192")} .env.local \uC5D0 SUPABASE_URL, SUPABASE_ANON_KEY \uC785\uB825`);
7181
+ }
7182
+ if (extraTools.includes("mcp") && interview.mcp) {
7183
+ console.log(` ${chalk9.yellow("\u2192")} mcp-server/ \uC5D0\uC11C MCP \uC11C\uBC84 \uAC1C\uBC1C \uC2DC\uC791`);
7184
+ }
7185
+ if (interview.recipes && interview.recipes.length > 0) {
7186
+ for (const recipe of interview.recipes) {
7187
+ console.log(` ${chalk9.yellow("\u2192")} docs/recipes/${recipe}/ \uAC00\uC774\uB4DC \uD655\uC778`);
7188
+ }
7189
+ }
7190
+ console.log(` ${chalk9.yellow("\u2192")} docs/openspec.md \uC5D0 PRD \uC791\uC131 \uC2DC\uC791`);
7191
+ console.log("");
6323
7192
  const { runEval } = await inquirer.prompt([{
6324
7193
  type: "confirm",
6325
7194
  name: "runEval",
@@ -6341,10 +7210,10 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
6341
7210
  printReport2(evalResult);
6342
7211
  await sleep2(500);
6343
7212
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6344
- const reportDir = join13(projectDir, "docs", "p-reports");
7213
+ const reportDir = join21(projectDir, "docs", "p-reports");
6345
7214
  await fs21.ensureDir(reportDir);
6346
7215
  const detailedReport = buildDetailedReport3(evalResult, projectName);
6347
- await fs21.writeFile(join13(reportDir, `${today}.md`), detailedReport, "utf8");
7216
+ await fs21.writeFile(join21(reportDir, `${today}.md`), detailedReport, "utf8");
6348
7217
  await sleep2(500);
6349
7218
  console.log("");
6350
7219
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
@@ -6372,7 +7241,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
6372
7241
  const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
6373
7242
  const omcSpinner = createSpinner2("OMC \uD50C\uB7EC\uADF8\uC778 \uB4F1\uB85D \uC911...");
6374
7243
  try {
6375
- const { enableOmcPlugin: enableOmcPlugin2 } = await Promise.resolve().then(() => (init_claude_settings(), claude_settings_exports));
7244
+ const { enableOmcPlugin: enableOmcPlugin2 } = await Promise.resolve().then(() => (init_claude_settings2(), claude_settings_exports));
6376
7245
  const result = await enableOmcPlugin2();
6377
7246
  if (result.action === "already-enabled") {
6378
7247
  omcSpinner.succeed("OMC \uD50C\uB7EC\uADF8\uC778 \uC774\uBBF8 \uB4F1\uB85D\uB428");
@@ -6417,7 +7286,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
6417
7286
  const aliasLine = getYoloAliasLine2();
6418
7287
  const rcContent = await fs21.readFile(shellRc, "utf8").catch(() => "");
6419
7288
  if (!rcContent.includes("claude-yolo")) {
6420
- await fs21.ensureDir(join13(shellRc, ".."));
7289
+ await fs21.ensureDir(join21(shellRc, ".."));
6421
7290
  await fs21.appendFile(shellRc, `
6422
7291
  # PAI \u2014 claude-YOLO mode
6423
7292
  ${aliasLine}
@@ -6611,9 +7480,9 @@ async function installOrchestratorOnly(projectDir, projectName) {
6611
7480
  const evalResult = computeResult2(llmOutput);
6612
7481
  printReport2(evalResult);
6613
7482
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6614
- const reportDir = join13(projectDir, "docs", "p-reports");
7483
+ const reportDir = join21(projectDir, "docs", "p-reports");
6615
7484
  await fs21.ensureDir(reportDir);
6616
- await fs21.writeFile(join13(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
7485
+ await fs21.writeFile(join21(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
6617
7486
  console.log("");
6618
7487
  hint(`\uC0C1\uC138 \uB9AC\uD3EC\uD2B8: docs/p-reports/${today}.md`);
6619
7488
  } catch {
@@ -6663,7 +7532,7 @@ async function detectLegacyProject(cwd) {
6663
7532
  ".gitignore"
6664
7533
  ];
6665
7534
  for (const signal of signals) {
6666
- if (await fs21.pathExists(join13(cwd, signal))) return true;
7535
+ if (await fs21.pathExists(join21(cwd, signal))) return true;
6667
7536
  }
6668
7537
  return false;
6669
7538
  }
@@ -6676,6 +7545,7 @@ var init_init_cmd = __esm({
6676
7545
  init_detector();
6677
7546
  init_roboco();
6678
7547
  init_validate_cmd();
7548
+ init_interviewer();
6679
7549
  init_ui();
6680
7550
  init_logger();
6681
7551
  }
@@ -6756,12 +7626,12 @@ var init_help_cmd = __esm({
6756
7626
  });
6757
7627
 
6758
7628
  // src/stages/design/openspec.ts
6759
- import { join as join14 } from "path";
7629
+ import { join as join22 } from "path";
6760
7630
  import fs22 from "fs-extra";
6761
7631
  async function initOpenSpec(cwd, projectName) {
6762
- const docsDir = join14(cwd, "docs");
7632
+ const docsDir = join22(cwd, "docs");
6763
7633
  await fs22.ensureDir(docsDir);
6764
- const openspecPath = join14(docsDir, "openspec.md");
7634
+ const openspecPath = join22(docsDir, "openspec.md");
6765
7635
  if (await fs22.pathExists(openspecPath)) {
6766
7636
  info("docs/openspec.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
6767
7637
  return;
@@ -6794,9 +7664,9 @@ async function initOpenSpec(cwd, projectName) {
6794
7664
  }
6795
7665
  async function validateOpenSpec(cwd) {
6796
7666
  const candidates = [
6797
- join14(cwd, "docs", "openspec.md"),
6798
- join14(cwd, "openspec.md"),
6799
- join14(cwd, ".pai", "openspec.md")
7667
+ join22(cwd, "docs", "openspec.md"),
7668
+ join22(cwd, "openspec.md"),
7669
+ join22(cwd, ".pai", "openspec.md")
6800
7670
  ];
6801
7671
  let specPath = null;
6802
7672
  for (const p of candidates) {
@@ -6862,12 +7732,12 @@ var init_openspec = __esm({
6862
7732
  });
6863
7733
 
6864
7734
  // src/stages/design/omc.ts
6865
- import { join as join15 } from "path";
7735
+ import { join as join23 } from "path";
6866
7736
  import fs23 from "fs-extra";
6867
7737
  async function initOMC(cwd, projectName) {
6868
- const paiDir = join15(cwd, ".pai");
7738
+ const paiDir = join23(cwd, ".pai");
6869
7739
  await fs23.ensureDir(paiDir);
6870
- const omcPath = join15(paiDir, "omc.md");
7740
+ const omcPath = join23(paiDir, "omc.md");
6871
7741
  if (await fs23.pathExists(omcPath)) {
6872
7742
  info(".pai/omc.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
6873
7743
  return;
@@ -7031,13 +7901,13 @@ var init_context = __esm({
7031
7901
  });
7032
7902
 
7033
7903
  // src/stages/design/index.ts
7034
- import { join as join16 } from "path";
7904
+ import { join as join24 } from "path";
7035
7905
  import fs24 from "fs-extra";
7036
7906
  async function autoInstallHarness(cwd) {
7037
- const harnessPath = join16(cwd, ".pai", "harness.json");
7907
+ const harnessPath = join24(cwd, ".pai", "harness.json");
7038
7908
  if (await fs24.pathExists(harnessPath)) return;
7039
7909
  await withSpinner("Harness Engineering \uC790\uB3D9 \uC124\uC815 \uC911...", async () => {
7040
- await fs24.ensureDir(join16(cwd, ".pai"));
7910
+ await fs24.ensureDir(join24(cwd, ".pai"));
7041
7911
  await fs24.writeJson(harnessPath, {
7042
7912
  version: "1.0",
7043
7913
  specFile: "docs/openspec.md",
@@ -7754,7 +8624,7 @@ var savetoken_cmd_exports = {};
7754
8624
  __export(savetoken_cmd_exports, {
7755
8625
  savetokenCommand: () => savetokenCommand
7756
8626
  });
7757
- import { join as join17, relative } from "path";
8627
+ import { join as join25, relative } from "path";
7758
8628
  import fs26 from "fs-extra";
7759
8629
  import chalk7 from "chalk";
7760
8630
  async function savetokenCommand(cwd) {
@@ -7824,10 +8694,10 @@ async function savetokenCommand(cwd) {
7824
8694
  console.log(` \u2192 ${colors.dim("\uB8F0 \uAE30\uBC18 \uB85C\uC9C1, \uD0A4\uC6CC\uB4DC \uB9E4\uCE6D\uC73C\uB85C \uAC80\uD1A0 \uD6C4 \uB300\uCCB4")}`);
7825
8695
  console.log(` ${chalk7.red("\u25CF")} \uB192\uC74C \uCF54\uB4DC \uC0DD\uC131, \uBCF5\uC7A1\uD55C \uCD94\uB860, \uCC3D\uC758\uC801 \uC0DD\uC131`);
7826
8696
  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");
8697
+ const reportDir = join25(cwd, ".pai");
7828
8698
  await fs26.ensureDir(reportDir);
7829
8699
  const report = buildReport(callSites, cwd);
7830
- const reportPath = join17(reportDir, "savetoken-report.md");
8700
+ const reportPath = join25(reportDir, "savetoken-report.md");
7831
8701
  await fs26.writeFile(reportPath, report, "utf8");
7832
8702
  console.log("");
7833
8703
  success("\uC2A4\uCE94 \uB9AC\uD3EC\uD2B8 \uC800\uC7A5: .pai/savetoken-report.md");
@@ -7984,7 +8854,7 @@ var wakeup_cmd_exports = {};
7984
8854
  __export(wakeup_cmd_exports, {
7985
8855
  wakeupCommand: () => wakeupCommand
7986
8856
  });
7987
- import { join as join18 } from "path";
8857
+ import { join as join26 } from "path";
7988
8858
  import { homedir as homedir3, platform as osPlatform } from "os";
7989
8859
  import fs27 from "fs-extra";
7990
8860
  import chalk8 from "chalk";
@@ -8065,7 +8935,7 @@ async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
8065
8935
  async function setupMacOS(config) {
8066
8936
  const { execa } = await import("execa");
8067
8937
  const [hour, minute] = config.time.split(":").map(Number);
8068
- const plistDir = join18(homedir3(), "Library", "LaunchAgents");
8938
+ const plistDir = join26(homedir3(), "Library", "LaunchAgents");
8069
8939
  await fs27.ensureDir(plistDir);
8070
8940
  const weekdays = scheduleToWeekdays(config.schedule);
8071
8941
  let calendarEntries;
@@ -8124,9 +8994,9 @@ ${calendarEntries}
8124
8994
  async function setupWindows(config) {
8125
8995
  const { execa } = await import("execa");
8126
8996
  const [hour, minute] = config.time.split(":").map(Number);
8127
- const psScriptDir = join18(homedir3(), ".pai");
8997
+ const psScriptDir = join26(homedir3(), ".pai");
8128
8998
  await fs27.ensureDir(psScriptDir);
8129
- const psScriptPath = join18(psScriptDir, "wakeup.ps1");
8999
+ const psScriptPath = join26(psScriptDir, "wakeup.ps1");
8130
9000
  const claudeCmd = config.launchMode === "yolo" ? "claude --dangerously-skip-permissions" : "claude";
8131
9001
  const psScript = `# PAI Wakeup \u2014 Claude Code \uC138\uC158 \uC790\uB3D9 \uC2DC\uC791
8132
9002
  $paiDir = "$env:USERPROFILE\\.pai"
@@ -8400,12 +9270,12 @@ var init_wakeup_cmd = __esm({
8400
9270
  "use strict";
8401
9271
  init_ui();
8402
9272
  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");
9273
+ PAI_DIR2 = join26(homedir3(), ".pai");
9274
+ CONFIG_FILE2 = join26(PAI_DIR2, "wakeup-config.json");
9275
+ MESSAGES_FILE = join26(PAI_DIR2, "wakeup-messages.json");
9276
+ SCRIPT_FILE = join26(PAI_DIR2, "wakeup.sh");
8407
9277
  PLIST_NAME = "com.pai.wakeup";
8408
- PLIST_PATH = join18(homedir3(), "Library", "LaunchAgents", `${PLIST_NAME}.plist`);
9278
+ PLIST_PATH = join26(homedir3(), "Library", "LaunchAgents", `${PLIST_NAME}.plist`);
8409
9279
  CRON_MARKER = "# PAI-WAKEUP";
8410
9280
  MESSAGES = [
8411
9281
  `Here's to the crazy ones. The misfits. The rebels. The troublemakers.