pai-zero 0.11.5 → 0.12.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
@@ -441,6 +441,61 @@ var init_analyzer = __esm({
441
441
  }
442
442
  });
443
443
 
444
+ // src/core/recipes.ts
445
+ var recipes_exports = {};
446
+ __export(recipes_exports, {
447
+ RECIPES: () => RECIPES,
448
+ getRecipe: () => getRecipe,
449
+ listRecipeKeys: () => listRecipeKeys
450
+ });
451
+ function listRecipeKeys() {
452
+ return Object.keys(RECIPES);
453
+ }
454
+ function getRecipe(key) {
455
+ return RECIPES[key.toLowerCase()];
456
+ }
457
+ var RECIPES;
458
+ var init_recipes = __esm({
459
+ "src/core/recipes.ts"() {
460
+ "use strict";
461
+ RECIPES = {
462
+ oauth: {
463
+ label: "\uC0AC\uB0B4 OAuth2 \uC778\uC99D",
464
+ description: "\uC0AC\uB0B4 SSO / \uC778\uC99D \uD3EC\uD138 \uC5F0\uACB0 \uAC00\uC774\uB4DC + \uC0D8\uD50C \uCF54\uB4DC",
465
+ source: {
466
+ repo: "SoInKyu/setup-repo",
467
+ ref: "main",
468
+ path: "repo/oauth"
469
+ },
470
+ target: "docs/recipes/oauth",
471
+ envKeys: [
472
+ { key: "OAUTH_HOST", hint: "\uC608: auth.mycompany.com" },
473
+ { key: "OAUTH_CLIENT_ID", hint: "\uC778\uC99D \uD3EC\uD138\uC5D0\uC11C \uBC1C\uAE09\uBC1B\uC740 Client ID" },
474
+ { key: "OAUTH_CLIENT_SECRET", hint: "\uC778\uC99D \uD3EC\uD138\uC5D0\uC11C \uBC1C\uAE09\uBC1B\uC740 Client Secret" },
475
+ { key: "OAUTH_REDIRECT_URI", hint: "\uC608: http://localhost:3000/auth/callback", default: "http://localhost:3000/auth/callback" }
476
+ ],
477
+ skillDescription: "\uC0AC\uB0B4 OAuth2 \uC778\uC99D / SSO / \uB85C\uADF8\uC778 \uAD6C\uD604 \uC2DC \uC774 \uB808\uC2DC\uD53C \uBB38\uC11C\uB97C \uC6B0\uC120 \uCC38\uC870"
478
+ },
479
+ officechat: {
480
+ label: "\uBA54\uC2E0\uC800 \uCC57\uBD07 (Office Chat)",
481
+ description: "\uC0AC\uB0B4 \uBA54\uC2E0\uC800 \uCC57\uBD07 \uC5F0\uACB0 \uAC00\uC774\uB4DC + \uC0D8\uD50C \uCF54\uB4DC",
482
+ source: {
483
+ repo: "SoInKyu/setup-repo",
484
+ ref: "main",
485
+ path: "repo/officechat"
486
+ },
487
+ target: "docs/recipes/officechat",
488
+ envKeys: [
489
+ { key: "OFFICECHAT_HOST", hint: "\uC0AC\uB0B4 \uBA54\uC2E0\uC800 \uC11C\uBC84 \uC8FC\uC18C" },
490
+ { key: "OFFICECHAT_TOKEN", hint: "\uBD07 \uD1A0\uD070" },
491
+ { key: "OFFICECHAT_CHANNEL", hint: "\uAE30\uBCF8 \uCC44\uB110 ID" }
492
+ ],
493
+ skillDescription: "\uBA54\uC2E0\uC800 \uCC57\uBD07 / \uC54C\uB9BC \uBD07 / Office Chat \uC5F0\uB3D9 \uAD6C\uD604 \uC2DC \uC774 \uB808\uC2DC\uD53C \uBB38\uC11C\uB97C \uC6B0\uC120 \uCC38\uC870"
494
+ }
495
+ };
496
+ }
497
+ });
498
+
444
499
  // src/stages/environment/interviewer.ts
445
500
  async function runInterview(analysis, cwd, projectName) {
446
501
  try {
@@ -531,7 +586,7 @@ function parseAiResult(result, analysis, projectName) {
531
586
  }
532
587
  async function interactiveInterview(analysis, _cwd, projectName) {
533
588
  const { default: inquirer } = await import("inquirer");
534
- const chalk8 = (await import("chalk")).default;
589
+ const chalk9 = (await import("chalk")).default;
535
590
  step(1, 3, "\uD504\uB85C\uC81D\uD2B8 \uC218\uC900\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694");
536
591
  hint("\uC120\uD0DD\uC5D0 \uB530\uB77C \uC124\uCE58\uB418\uB294 \uD50C\uB7EC\uADF8\uC778 \uC138\uD2B8\uAC00 \uB2EC\uB77C\uC9D1\uB2C8\uB2E4.");
537
592
  console.log("");
@@ -593,7 +648,7 @@ async function interactiveInterview(analysis, _cwd, projectName) {
593
648
  { name: "Naver \uB85C\uADF8\uC778", value: "naver" },
594
649
  { name: `\uC0AC\uB0B4 \uC778\uC99D ${colors.dim("(OAuth2)")}`, value: "custom" },
595
650
  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"),
596
- { name: chalk8.green("\u21B5 \uC120\uD0DD \uC644\uB8CC"), value: "__done__" }
651
+ { name: chalk9.green("\u21B5 \uC120\uD0DD \uC644\uB8CC"), value: "__done__" }
597
652
  ],
598
653
  validate: (answer) => {
599
654
  const items = answer.filter((a) => a !== "__done__");
@@ -705,6 +760,28 @@ async function interactiveInterview(analysis, _cwd, projectName) {
705
760
  };
706
761
  extraTools.push("mcp");
707
762
  }
763
+ const { RECIPES: RECIPES2 } = await Promise.resolve().then(() => (init_recipes(), recipes_exports));
764
+ const recipeKeys = Object.keys(RECIPES2);
765
+ let selectedRecipes = [];
766
+ if (recipeKeys.length > 0) {
767
+ console.log("");
768
+ hint("Space\uB85C \uC120\uD0DD \xB7 \uC120\uD0DD \uD6C4 Enter\uB85C \uC644\uB8CC (\uAC74\uB108\uB6F0\uB824\uBA74 \uBC14\uB85C Enter)");
769
+ const Separator2 = inquirer.Separator;
770
+ const recipeAnswer = await inquirer.prompt([{
771
+ type: "checkbox",
772
+ name: "recipes",
773
+ message: "\uC0AC\uB0B4 \uC2DC\uC2A4\uD15C \uC5F0\uB3D9 \uB808\uC2DC\uD53C\uB97C \uC124\uCE58\uD560\uAE4C\uC694?",
774
+ choices: [
775
+ ...recipeKeys.map((k) => ({
776
+ name: `${RECIPES2[k].label} ${colors.dim("\u2500 " + RECIPES2[k].description)}`,
777
+ value: k
778
+ })),
779
+ 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"),
780
+ { name: chalk9.green("\u21B5 \uC120\uD0DD \uC644\uB8CC (\uAC74\uB108\uB6F0\uAE30)"), value: "__done__" }
781
+ ]
782
+ }]);
783
+ selectedRecipes = recipeAnswer.recipes.filter((x) => x !== "__done__");
784
+ }
708
785
  step(3, 3, "\uC124\uCE58 \uD655\uC778");
709
786
  console.log("");
710
787
  console.log(colors.dim(" \uC124\uCE58\uB420 \uD56D\uBAA9"));
@@ -729,6 +806,10 @@ async function interactiveInterview(analysis, _cwd, projectName) {
729
806
  console.log("");
730
807
  console.log(` ${colors.accent("+")} \uB85C\uADF8\uC778: ${authMethods.join(", ")}`);
731
808
  }
809
+ if (selectedRecipes.length > 0) {
810
+ console.log("");
811
+ console.log(` ${colors.accent("+")} \uC0AC\uB0B4 \uC5F0\uB3D9 \uB808\uC2DC\uD53C: ${selectedRecipes.join(", ")}`);
812
+ }
732
813
  console.log("");
733
814
  const { proceed } = await inquirer.prompt([{
734
815
  type: "confirm",
@@ -749,6 +830,7 @@ async function interactiveInterview(analysis, _cwd, projectName) {
749
830
  authMethods,
750
831
  customAuth,
751
832
  extraTools,
833
+ recipes: selectedRecipes,
752
834
  mcp,
753
835
  setupDomains: {
754
836
  claudeEnv: true,
@@ -808,20 +890,22 @@ function generateClaudeMd(cwd, analysis, interview) {
808
890
 
809
891
  ## Project Overview
810
892
 
811
- Describe your project here.
893
+ <!-- TODO: \uC774 \uD504\uB85C\uC81D\uD2B8\uAC00 \uBB34\uC5C7\uC744 \uD574\uACB0\uD558\uB294\uC9C0 2-3\uBB38\uC7A5\uC73C\uB85C \uAE30\uC220. \uC0C1\uC138 \uB0B4\uC6A9\uC740 docs/openspec.md \uCC38\uC870. -->
894
+
895
+ \uC790\uC138\uD55C \uC81C\uD488 \uC694\uAD6C\uC0AC\uD56D: \`docs/openspec.md\` \xB7 \uB3C4\uBA54\uC778 \uBAA8\uB378: \`.pai/omc.md\`
812
896
 
813
897
  <pai>
814
898
  ## PAI Configuration
815
899
 
816
900
  This project uses PAI (Plugin-based AI) for structured AI development.
817
901
 
818
- ### Pipeline: \uC124\uACC4-\uC2E4\uD589-\uAC80\uC99D-\uD3C9\uAC00-\uD658\uACBD
902
+ ### Pipeline: Environment \u2192 Design \u2192 Execution \u2192 Validation \u2192 Evaluation
819
903
 
820
- 1. **Environment** \u2014 \uD504\uB85C\uC81D\uD2B8 \uD658\uACBD \uAD6C\uC131 (\uC774 \uB2E8\uACC4)
821
- 2. **Design** \u2014 OpenSpec/OMC \uAE30\uBC18 \uC124\uACC4
822
- 3. **Execution** \u2014 AI \uCF54\uB4DC \uC0DD\uC131
823
- 4. **Validation** \u2014 gstack \uD14C\uC2A4\uD2B8 / harness \uAC80\uC99D
824
- 5. **Evaluation** \u2014 \uD488\uC9C8 \uD3C9\uAC00 (6\uCE74\uD14C\uACE0\uB9AC)
904
+ 1. **Environment** \u2014 \uD504\uB85C\uC81D\uD2B8 \uD658\uACBD \uAD6C\uC131 (\uC644\uB8CC)
905
+ 2. **Design** \u2014 OpenSpec (\`docs/openspec.md\`) + OMC (\`.pai/omc.md\`) \uAE30\uBC18 \uC124\uACC4
906
+ 3. **Execution** \u2014 AI \uCF54\uB4DC \uC0DD\uC131 (\uC774 \uB2E8\uACC4 Claude\uAC00 \uB2F4\uB2F9)
907
+ 4. **Validation** \u2014 gstack \uD14C\uC2A4\uD2B8 + harness(\uC124\uACC4\u2194\uAD6C\uD604) \uAC80\uC99D
908
+ 5. **Evaluation** \u2014 6\uCE74\uD14C\uACE0\uB9AC \uD488\uC9C8 \uD3C9\uAC00
825
909
 
826
910
  ### Stack
827
911
  - **Languages**: ${stack}
@@ -829,15 +913,30 @@ This project uses PAI (Plugin-based AI) for structured AI development.
829
913
  - **Mode**: ${interview.mode}
830
914
  - **Config**: \`.pai/config.json\`
831
915
 
832
- Run \`pai env status\` to check setup, \`pai evaluate\` for quality scoring.
916
+ ### \uD50C\uB7EC\uADF8\uC778 \uAC04 \uACC4\uC57D (\uD30C\uC774\uD504\uB77C\uC778 \uC5E3\uC9C0)
917
+
918
+ \`.pai/config.json\`\uC758 \`edges\` \uBC30\uC5F4\uC774 \uD50C\uB7EC\uADF8\uC778 \uAC04 \uBA85\uC2DC\uC801 \uACC4\uC57D\uC744 \uC815\uC758\uD569\uB2C8\uB2E4.
919
+ \uCF54\uB4DC \uC791\uC131 \uC2DC \uBC18\uB4DC\uC2DC \uACC4\uC57D\uC744 \uC9C0\uD0A4\uC138\uC694:
920
+
921
+ - **harness \u2192 openspec**: \`docs/openspec.md\`\uC758 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uD14C\uC774\uBE14\uC774 \uAD6C\uD604\uACFC \uC77C\uCE58\uD574\uC57C \uD568 (\`pai test\` \uC2E4\uD328)
922
+ - **gstack \u2192 openspec**: \uAC01 \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uB294 \uD14C\uC2A4\uD2B8\uAC00 \uC874\uC7AC\uD574\uC57C \uD568
923
+ - **omc \u2192 openspec**: \uB3C4\uBA54\uC778 \uAC1D\uCCB4\uB294 API \uC694\uCCAD/\uC751\uB2F5 \uBAA8\uB378\uACFC \uC815\uD569 (\uC0C8 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uCD94\uAC00 \uC2DC omc.md\uB3C4 \uC5C5\uB370\uC774\uD2B8)
924
+ - **harness \u2192 omc**: \`.pai/omc.md\`\uC758 \uBE44\uC988\uB2C8\uC2A4 \uADDC\uCE59\uC774 \uCF54\uB4DC\uB85C \uAD6C\uD604\uB418\uC5B4\uC57C \uD568
833
925
 
834
926
  ### Global Rules
835
927
 
836
- - **\uC2EC\uCE35 \uC778\uD130\uBDF0 \uC790\uB3D9 \uC218\uD589**: \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uBD80\uC871\uD558\uAC70\uB098 \uBAA8\uD638\uD55C \uACBD\uC6B0, AskUserQuestion \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC5EC \uC2EC\uCE35 \uC778\uD130\uBDF0\uB97C \uC790\uB3D9\uC73C\uB85C \uC9C4\uD589\uD569\uB2C8\uB2E4. \uCD94\uCE21\uD558\uC9C0 \uB9D0\uACE0 \uBC18\uB4DC\uC2DC \uC9C8\uBB38\uD558\uC138\uC694.
837
- - **Ideation \u2192 PRD \uD30C\uC774\uD504\uB77C\uC778**: \`/pai design\` \uC2E4\uD589 \uC2DC ideation.md \u2192 openspec.md \uC21C\uC11C\uB85C \uC2EC\uCE35 \uC778\uD130\uBDF0\uB97C \uD1B5\uD574 \uC791\uC131\uD569\uB2C8\uB2E4.
838
- - **\uAE30\uC220 \uC81C\uC57D \uD30C\uC545**: \uCF54\uB4DC \uC0DD\uC131 \uC804 \uAE30\uC220\uC801 \uC81C\uC57D \uC0AC\uD56D\uC744 \uC2EC\uCE35 \uC778\uD130\uBDF0\uB85C \uD655\uC778\uD569\uB2C8\uB2E4.
839
- - **\uD558\uB124\uC2A4 \uC5D4\uC9C0\uB2C8\uC5B4\uB9C1**: AI \uC5D0\uC774\uC804\uD2B8\uAC00 \uC548\uC804\uD558\uAC8C \uC791\uC5C5\uD560 \uC218 \uC788\uB3C4\uB85D \uD14C\uC2A4\uD2B8/\uAC80\uC99D \uD658\uACBD\uC744 \uAD6C\uC131\uD569\uB2C8\uB2E4.
840
- - **Git \uCEE4\uBC0B \uADDC\uCE59**: \uC791\uC5C5 \uB2E8\uC704\uBCC4\uB85C \uC758\uBBF8 \uC788\uB294 \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB97C \uC791\uC131\uD569\uB2C8\uB2E4.
928
+ - **\uC2EC\uCE35 \uC778\uD130\uBDF0 \uC790\uB3D9 \uC218\uD589**: \uBAA8\uD638\uD55C \uACBD\uC6B0 AskUserQuestion\uC73C\uB85C \uC989\uC2DC \uC9C8\uBB38. \uCD94\uCE21 \uAE08\uC9C0.
929
+ - **\uC124\uACC4 \uBA3C\uC800**: \uCF54\uB4DC \uC804 \`docs/openspec.md\`\uB97C \uBA3C\uC800 \uC77D\uACE0, \uD544\uC694\uD55C \uC5D4\uB4DC\uD3EC\uC778\uD2B8/\uBAA8\uB378\uC744 \uC815\uC758.
930
+ - **\uACC4\uC57D \uC591\uBC29\uD5A5 \uC5C5\uB370\uC774\uD2B8**: API \uCD94\uAC00 \uC2DC openspec.md + omc.md \uB3D9\uC2DC \uC218\uC815. \uD558\uB098\uB9CC \uC5C5\uB370\uC774\uD2B8\uD558\uB294 PR \uAE08\uC9C0.
931
+ - **\uAC80\uC99D \uC6B0\uC120**: \`pai test\` \uC2E4\uD328 \uC2DC \uC218\uC815 \uBC94\uC704\uB97C \uC881\uD788\uACE0 \uC6D0\uC778\uC744 \uC815\uD655\uD788 \uC774\uD574\uD560 \uAC83. \`--skip\`/ disable \uAE08\uC9C0.
932
+ - **Git \uCEE4\uBC0B \uADDC\uCE59**: \uC791\uC5C5 \uB2E8\uC704\uBCC4\uB85C \uC758\uBBF8 \uC788\uB294 \uCEE4\uBC0B \uBA54\uC2DC\uC9C0.
933
+
934
+ ### \uBA85\uB839\uC5B4
935
+
936
+ - \`pai status\` \u2014 \uD604\uC7AC \uD50C\uB7EC\uADF8\uC778/\uBAA8\uB4DC \uD655\uC778
937
+ - \`pai test\` \u2014 \uD14C\uC2A4\uD2B8 + \uD558\uB124\uC2A4 \uAC80\uC99D
938
+ - \`pai grade\` \u2014 6\uCE74\uD14C\uACE0\uB9AC \uD488\uC9C8 \uD3C9\uAC00
939
+ - \`pai add\` \u2014 \uD50C\uB7EC\uADF8\uC778 \uCD94\uAC00 / \uBAA8\uB4DC \uC2B9\uACA9
841
940
 
842
941
  ### Contribution Guide
843
942
 
@@ -2162,54 +2261,189 @@ async function provisionSupabase(ctx) {
2162
2261
  async function provisionOpenSpec(ctx) {
2163
2262
  await fs7.ensureDir(join4(ctx.cwd, "docs"));
2164
2263
  const openspecPath = join4(ctx.cwd, "docs", "openspec.md");
2165
- if (!await fs7.pathExists(openspecPath)) {
2166
- await fs7.writeFile(openspecPath, [
2167
- `# OpenSpec \u2014 ${ctx.projectName}`,
2168
- "",
2169
- "## 1. \uBAA9\uC801 (Purpose)",
2170
- "> \uC774 \uC11C\uBE44\uC2A4\uB294 \uBB34\uC5C7\uC744 \uD574\uACB0\uD558\uB294\uAC00?",
2171
- "",
2172
- "## 2. \uC0AC\uC6A9\uC790 (Users)",
2173
- "| \uC5ED\uD560 | \uC124\uBA85 |",
2174
- "|------|------|",
2175
- "| - | - |",
2176
- "",
2177
- "## 3. \uD575\uC2EC \uAE30\uB2A5 (Features)",
2178
- "- [ ] Feature 1",
2264
+ if (await fs7.pathExists(openspecPath)) return;
2265
+ const langs = ctx.analysis?.stack.languages ?? [];
2266
+ const frameworks = ctx.analysis?.stack.frameworks ?? [];
2267
+ const pkgMgr = ctx.analysis?.stack.packageManager;
2268
+ const frontendFrameworks = frameworks.filter(
2269
+ (f) => ["React", "Vue", "Next.js", "Nuxt", "Svelte", "Angular"].includes(f)
2270
+ );
2271
+ const backendFrameworks = frameworks.filter(
2272
+ (f) => ["Express", "Fastify", "NestJS", "Koa", "Django", "Flask", "FastAPI", "Rails", "Spring"].includes(f)
2273
+ );
2274
+ const frontendLine = frontendFrameworks.length > 0 ? frontendFrameworks.join(" + ") : "<!-- TODO: \uD504\uB860\uD2B8\uC5D4\uB4DC \uD504\uB808\uC784\uC6CC\uD06C (React / Vue / Next.js \uB4F1) -->";
2275
+ const backendLine = backendFrameworks.length > 0 ? backendFrameworks.join(" + ") : langs.includes("TypeScript") || langs.includes("JavaScript") ? "<!-- TODO: Node.js \uBC31\uC5D4\uB4DC \uD504\uB808\uC784\uC6CC\uD06C (Express / Fastify / NestJS \uB4F1) -->" : "<!-- TODO: \uBC31\uC5D4\uB4DC \uD504\uB808\uC784\uC6CC\uD06C -->";
2276
+ const modeHint = ctx.mode === "prototype" ? "\uD504\uB85C\uD1A0\uD0C0\uC785 \u2014 \uBE60\uB978 \uAC80\uC99D \uBAA9\uC801. \uD575\uC2EC \uC2DC\uB098\uB9AC\uC624 1-2\uAC1C\uB9CC \uC815\uC758\uD574\uB3C4 \uCDA9\uBD84." : ctx.mode === "poc" ? "PoC \u2014 \uB3D9\uC791 \uB370\uBAA8. \uC8FC\uC694 \uAE30\uB2A5 3-5\uAC1C + \uD575\uC2EC API \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uC815\uC758 \uD544\uC694." : "\uC6B4\uC601 \u2014 \uD14C\uC2A4\uD2B8/QA \uB300\uC0C1. \uBAA8\uB4E0 \uAE30\uB2A5\uACFC API \uACC4\uC57D\uC744 \uC644\uC804\uD788 \uC815\uC758\uD574\uC57C \uD568.";
2277
+ await fs7.writeFile(openspecPath, [
2278
+ `# OpenSpec \u2014 ${ctx.projectName}`,
2279
+ "",
2280
+ `> **\uBAA8\uB4DC**: ${ctx.mode} \xB7 ${modeHint}`,
2281
+ `> **\uC704\uCE58**: \uC774 \uBB38\uC11C\uAC00 \uD30C\uC774\uD504\uB77C\uC778\uC758 \uC911\uC559 \uD5C8\uBE0C\uC785\uB2C8\uB2E4 \u2014 gstack, harness, omc\uAC00 \uBAA8\uB450 \uCC38\uC870.`,
2282
+ `> **\uC791\uC131 \uAC00\uC774\uB4DC**: \`/pai design\` \uC2E4\uD589 \uC2DC Claude\uAC00 \uC2EC\uCE35 \uC778\uD130\uBDF0\uB85C \uBE48 \uC139\uC158\uC744 \uCC44\uC6C1\uB2C8\uB2E4.`,
2283
+ "",
2284
+ "---",
2285
+ "",
2286
+ "## 1. \uBAA9\uC801 (Purpose)",
2287
+ "",
2288
+ "<!-- TODO: \uC774 \uC11C\uBE44\uC2A4\uAC00 \uBB34\uC5C7\uC744 \uD574\uACB0\uD558\uB294\uC9C0 2-3\uBB38\uC7A5\uC73C\uB85C \uAE30\uC220 -->",
2289
+ "<!-- \uC608\uC2DC:",
2290
+ " - \uB204\uAD6C\uC758 \uC5B4\uB5A4 \uBB38\uC81C\uB97C \uD574\uACB0\uD558\uB294\uAC00?",
2291
+ " - \uAE30\uC874 \uB300\uC548(\uC788\uB2E4\uBA74) \uB300\uBE44 \uCC28\uBCC4\uC810\uC740?",
2292
+ "-->",
2293
+ "",
2294
+ "## 2. \uC0AC\uC6A9\uC790 (Users)",
2295
+ "",
2296
+ "<!-- TODO: \uC2E4\uC81C \uC0AC\uC6A9\uC790 \uC5ED\uD560\uC744 \uC815\uC758 \u2014 role, \uAD8C\uD55C, \uC8FC\uC694 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 -->",
2297
+ "",
2298
+ "| \uC5ED\uD560 | \uAD8C\uD55C | \uC8FC\uC694 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 |",
2299
+ "|------|------|-------------------|",
2300
+ "| \uC77C\uBC18 \uC0AC\uC6A9\uC790 | \uC77D\uAE30/\uC4F0\uAE30 | (\uC608: \uAC8C\uC2DC\uAE00 \uC791\uC131, \uAC80\uC0C9) |",
2301
+ "| \uAD00\uB9AC\uC790 | \uC804\uCCB4 | (\uC608: \uC0AC\uC6A9\uC790 \uAD00\uB9AC, \uD1B5\uACC4 \uC870\uD68C) |",
2302
+ "",
2303
+ "## 3. \uD575\uC2EC \uAE30\uB2A5 (Features)",
2304
+ "",
2305
+ "<!-- TODO: MVP \uAE30\uB2A5 \uBAA9\uB85D. \uCCB4\uD06C\uBC15\uC2A4\uB85C \uC9C4\uD589\uC0C1\uD0DC \uD45C\uC2DC -->",
2306
+ "",
2307
+ "- [ ] \uC0AC\uC6A9\uC790 \uC778\uC99D (\uB85C\uADF8\uC778/\uB85C\uADF8\uC544\uC6C3)",
2308
+ "- [ ] \uD575\uC2EC \uAE30\uB2A5 1",
2309
+ "- [ ] \uD575\uC2EC \uAE30\uB2A5 2",
2310
+ "",
2311
+ "## 4. \uAE30\uC220 \uC2A4\uD0DD (Stack)",
2312
+ "",
2313
+ `- **Language**: ${langs.length > 0 ? langs.join(", ") : "<!-- TODO: \uC5B8\uC5B4 -->"}`,
2314
+ `- **Frontend**: ${frontendLine}`,
2315
+ `- **Backend**: ${backendLine}`,
2316
+ `- **Database**: <!-- TODO: PostgreSQL / MySQL / SQLite / MongoDB \uB4F1 -->`,
2317
+ `- **Package Manager**: ${pkgMgr ?? "<!-- TODO -->"}`,
2318
+ `- **Deployment**: <!-- TODO: Vercel / AWS / Docker \uB4F1 -->`,
2319
+ "",
2320
+ "## 5. API \uC5D4\uB4DC\uD3EC\uC778\uD2B8 (Endpoints)",
2321
+ "",
2322
+ "<!-- \uC911\uC694: \uC774 \uD45C\uAC00 gstack \uD14C\uC2A4\uD2B8 \uD0C0\uAC9F + harness \uAC80\uC99D \uD0C0\uAC9F\uC785\uB2C8\uB2E4.",
2323
+ " \uC2E0\uADDC \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uCD94\uAC00 \uC2DC \uBC18\uB4DC\uC2DC \uC5EC\uAE30 \uBA3C\uC800 \uC120\uC5B8. -->",
2324
+ "",
2325
+ "| Method | Path | \uC124\uBA85 | \uC694\uCCAD \uBAA8\uB378 | \uC751\uB2F5 \uBAA8\uB378 |",
2326
+ "|--------|------|------|-----------|-----------|",
2327
+ "| GET | /api/health | \uD5EC\uC2A4\uCCB4\uD06C | - | `{ ok: true }` |",
2328
+ "| POST | /api/auth/login | \uB85C\uADF8\uC778 | `{ email, password }` | `{ token }` |",
2329
+ "<!-- TODO: \uD504\uB85C\uC81D\uD2B8 \uACE0\uC720 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uCD94\uAC00 -->",
2330
+ "",
2331
+ "## 6. \uBE44\uAE30\uB2A5 \uC694\uAD6C\uC0AC\uD56D (\uC120\uD0DD)",
2332
+ "",
2333
+ "<!-- TODO: \uC131\uB2A5/\uBCF4\uC548/\uD655\uC7A5\uC131 \uC694\uAD6C \u2014 production \uBAA8\uB4DC\uC5D0\uC11C \uAD8C\uC7A5 -->",
2334
+ "",
2335
+ "---",
2336
+ "",
2337
+ "## \uD30C\uC774\uD504\uB77C\uC778 \uACC4\uC57D (\uC774 \uBB38\uC11C\uAC00 \uCC38\uC870\uB418\uB294 \uACF3)",
2338
+ "",
2339
+ "- `harness.json` \u2192 `specFile`\uB85C \uC774 \uBB38\uC11C \uCC38\uC870 (\uC124\uACC4\u2194\uAD6C\uD604 \uC77C\uCE58 \uAC80\uC99D)",
2340
+ "- `gstack.json` \u2192 \uAC01 \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uC5D0 \uB300\uD55C \uD14C\uC2A4\uD2B8 \uC874\uC7AC \uD655\uC778",
2341
+ "- `.pai/omc.md` \u2192 \uB3C4\uBA54\uC778 \uAC1D\uCCB4\uAC00 \uC694\uCCAD/\uC751\uB2F5 \uBAA8\uB378\uACFC \uC815\uD569\uD558\uB294\uC9C0 \uD655\uC778",
2342
+ ""
2343
+ ].join("\n") + "\n");
2344
+ }
2345
+ function buildOMCSkeleton(projectName, mode) {
2346
+ const lines = [
2347
+ `# OMC \u2014 Object Model Context (${projectName})`,
2348
+ "",
2349
+ `> **\uBAA8\uB4DC**: ${mode}`,
2350
+ "> **\uC5ED\uD560**: AI\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uB3C4\uBA54\uC778\uC744 \uC774\uD574\uD558\uAE30 \uC704\uD55C \uD575\uC2EC \uAC1D\uCCB4 \uBAA8\uB378.",
2351
+ "> **\uD30C\uC774\uD504\uB77C\uC778 \uACC4\uC57D**:",
2352
+ "> - \uC5EC\uAE30 \uC815\uC758\uB41C \uB3C4\uBA54\uC778 \uAC1D\uCCB4\uB294 `docs/openspec.md`\uC758 API \uC694\uCCAD/\uC751\uB2F5 \uBAA8\uB378\uACFC \uC815\uD569\uD574\uC57C \uD568",
2353
+ "> - `.pai/harness.json`\uC758 `businessRules` \uAC80\uC99D\uC774 \uC774 \uBB38\uC11C\uB97C \uCC38\uC870",
2354
+ "> **\uC791\uC131 \uAC00\uC774\uB4DC**: `/pai design` \uC2E4\uD589 \uC2DC Claude\uAC00 openspec.md \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uC5D0 \uB9DE\uCDB0 \uBE48 \uC139\uC158\uC744 \uCC44\uC6C1\uB2C8\uB2E4.",
2355
+ "",
2356
+ "---",
2357
+ "",
2358
+ "## \uB3C4\uBA54\uC778 \uAC1D\uCCB4",
2359
+ "",
2360
+ "### User",
2361
+ "",
2362
+ "```ts",
2363
+ "interface User {",
2364
+ " id: string;",
2365
+ " email: string;",
2366
+ " name: string;",
2367
+ " role: 'admin' | 'member';",
2368
+ " createdAt: Date;",
2369
+ "}",
2370
+ "```",
2371
+ "",
2372
+ "<!-- TODO: \uD504\uB85C\uC81D\uD2B8 \uACE0\uC720 \uB3C4\uBA54\uC778 \uAC1D\uCCB4 \uCD94\uAC00. openspec.md\uC758 \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uAC00 \uC694\uAD6C\uD558\uB294 \uBAA8\uB378\uC744 \uC5EC\uAE30 \uBA3C\uC800 \uC815\uC758 -->",
2373
+ ""
2374
+ ];
2375
+ if (mode === "poc" || mode === "production") {
2376
+ lines.push(
2377
+ "### Session",
2179
2378
  "",
2180
- "## 4. \uAE30\uC220 \uC2A4\uD0DD (Stack)",
2181
- "- Frontend:",
2182
- "- Backend:",
2183
- "- DB:",
2379
+ "```ts",
2380
+ "interface Session {",
2381
+ " token: string;",
2382
+ " userId: string;",
2383
+ " expiresAt: Date;",
2384
+ "}",
2385
+ "```",
2386
+ ""
2387
+ );
2388
+ }
2389
+ if (mode === "production") {
2390
+ lines.push(
2391
+ "### AuditLog",
2184
2392
  "",
2185
- "## 5. API \uC5D4\uB4DC\uD3EC\uC778\uD2B8 (Endpoints)",
2186
- "| Method | Path | \uC124\uBA85 |",
2187
- "|--------|------|------|",
2188
- "| GET | /api/health | \uD5EC\uC2A4\uCCB4\uD06C |"
2189
- ].join("\n") + "\n");
2393
+ "```ts",
2394
+ "interface AuditLog {",
2395
+ " id: string;",
2396
+ " userId: string;",
2397
+ " action: 'create' | 'update' | 'delete' | 'read';",
2398
+ " resource: string;",
2399
+ " at: Date;",
2400
+ " metadata?: Record<string, unknown>;",
2401
+ "}",
2402
+ "```",
2403
+ ""
2404
+ );
2405
+ }
2406
+ lines.push(
2407
+ "## \uBE44\uC988\uB2C8\uC2A4 \uADDC\uCE59",
2408
+ "",
2409
+ '<!-- \uADDC\uCE59\uC740 harness\uAC00 \uAC80\uC99D \uB300\uC0C1\uC73C\uB85C \uCC38\uC870. \uAC01 \uADDC\uCE59\uC740 "\uBB34\uC5C7\uC774 \uCC38\uC774\uC5B4\uC57C \uD558\uB294\uAC00"\uB85C \uAE30\uC220. -->',
2410
+ "",
2411
+ "- **\uC778\uC99D \uD544\uC218**: `User.id`\uAC00 \uC5C6\uB294 \uC694\uCCAD\uC740 \uAD00\uB9AC\uC790 API \uC811\uADFC \uBD88\uAC00",
2412
+ "- **\uC774\uBA54\uC77C \uC720\uC77C\uC131**: `User.email`\uC740 \uC2DC\uC2A4\uD15C \uC804\uCCB4\uC5D0\uC11C \uC720\uC77C"
2413
+ );
2414
+ if (mode === "poc" || mode === "production") {
2415
+ lines.push(
2416
+ "- **\uC138\uC158 \uB9CC\uB8CC**: `Session.expiresAt < now()` \uC774\uBA74 \uBAA8\uB4E0 API 401"
2417
+ );
2418
+ }
2419
+ if (mode === "production") {
2420
+ lines.push(
2421
+ "- **\uAC10\uC0AC \uB85C\uADF8**: \uBAA8\uB4E0 \uC4F0\uAE30 \uC561\uC158(create/update/delete)\uC740 `AuditLog`\uC5D0 \uAE30\uB85D",
2422
+ '- **\uAD8C\uD55C \uBD84\uB9AC**: `role === "member"` \uC0AC\uC6A9\uC790\uB294 \uB2E4\uB978 \uC0AC\uC6A9\uC790\uC758 \uB9AC\uC18C\uC2A4 \uC218\uC815 \uBD88\uAC00'
2423
+ );
2190
2424
  }
2425
+ lines.push(
2426
+ "",
2427
+ "<!-- TODO: \uD504\uB85C\uC81D\uD2B8 \uACE0\uC720 \uBE44\uC988\uB2C8\uC2A4 \uADDC\uCE59 \uCD94\uAC00. \uAC01 \uADDC\uCE59\uC740 \uAC00\uB2A5\uD558\uBA74 \uD14C\uC2A4\uD2B8 \uAC00\uB2A5\uD55C \uD615\uD0DC\uB85C. -->",
2428
+ "",
2429
+ "## \uB3C4\uBA54\uC778 - API \uB9E4\uD551 (\uC120\uD0DD)",
2430
+ "",
2431
+ "<!-- openspec.md \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uC640 \uB3C4\uBA54\uC778 \uAC1D\uCCB4 \uC5F0\uACB0. harness \uAC80\uC99D \uC6A9. -->",
2432
+ "",
2433
+ "| Endpoint | \uC694\uCCAD \uBAA8\uB378 | \uC751\uB2F5 \uBAA8\uB378 | \uBE44\uACE0 |",
2434
+ "|----------|-----------|-----------|------|",
2435
+ "| POST /api/auth/login | `{ email, password }` | `Session` | \uC131\uACF5 \uC2DC token \uBC1C\uAE09 |",
2436
+ "<!-- TODO: \uCD94\uAC00 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uB9E4\uD551 -->",
2437
+ ""
2438
+ );
2439
+ return lines.join("\n") + "\n";
2191
2440
  }
2192
2441
  async function provisionOMC(ctx) {
2193
2442
  await fs7.ensureDir(join4(ctx.cwd, ".pai"));
2194
2443
  const omcPath = join4(ctx.cwd, ".pai", "omc.md");
2195
2444
  if (!await fs7.pathExists(omcPath)) {
2196
- await fs7.writeFile(omcPath, [
2197
- `# OMC \u2014 Object Model Context (${ctx.projectName})`,
2198
- "",
2199
- "> AI\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uB3C4\uBA54\uC778\uC744 \uC774\uD574\uD558\uAE30 \uC704\uD55C \uD575\uC2EC \uAC1D\uCCB4 \uBAA8\uB378",
2200
- "",
2201
- "## \uB3C4\uBA54\uC778 \uAC1D\uCCB4",
2202
- "",
2203
- "### User",
2204
- "```",
2205
- "id: string",
2206
- "email: string",
2207
- 'role: "admin" | "member"',
2208
- "```",
2209
- "",
2210
- "## \uBE44\uC988\uB2C8\uC2A4 \uADDC\uCE59",
2211
- "- (\uC5EC\uAE30\uC5D0 \uD575\uC2EC \uBE44\uC988\uB2C8\uC2A4 \uB85C\uC9C1\uC744 \uAE30\uC220)"
2212
- ].join("\n") + "\n");
2445
+ const skeleton = buildOMCSkeleton(ctx.projectName, ctx.mode);
2446
+ await fs7.writeFile(omcPath, skeleton);
2213
2447
  }
2214
2448
  const omcDir = join4(ctx.cwd, ".omc");
2215
2449
  await fs7.ensureDir(omcDir);
@@ -3181,6 +3415,183 @@ description: "\uD50C\uB7EC\uADF8\uC778 \uCD94\uAC00 \uC124\uCE58 \u2014 Mockup\u
3181
3415
  }
3182
3416
  });
3183
3417
 
3418
+ // src/core/detector.ts
3419
+ var detector_exports = {};
3420
+ __export(detector_exports, {
3421
+ PLUGIN_META: () => PLUGIN_META,
3422
+ PLUGIN_SIGNATURES: () => PLUGIN_SIGNATURES,
3423
+ deriveEdges: () => deriveEdges,
3424
+ getBasePluginsForMode: () => getBasePluginsForMode,
3425
+ getPluginsForMode: () => getPluginsForMode,
3426
+ nextMode: () => nextMode,
3427
+ pluginsAddedByPromotion: () => pluginsAddedByPromotion,
3428
+ scanProjectState: () => scanProjectState
3429
+ });
3430
+ import path4 from "path";
3431
+ import fs9 from "fs-extra";
3432
+ async function scanProjectState(cwd) {
3433
+ const result = {
3434
+ isNewProject: true,
3435
+ hasPaiConfig: false,
3436
+ projectMode: null,
3437
+ installedPlugins: [],
3438
+ missingPlugins: [],
3439
+ details: {}
3440
+ };
3441
+ const paiConfigPath = path4.join(cwd, ".pai", "config.json");
3442
+ if (await fs9.pathExists(paiConfigPath)) {
3443
+ result.hasPaiConfig = true;
3444
+ try {
3445
+ const config = await fs9.readJson(paiConfigPath);
3446
+ result.projectMode = config.mode != null ? normalizeMode(config.mode) : null;
3447
+ } catch {
3448
+ }
3449
+ }
3450
+ for (const [key, signatures] of Object.entries(PLUGIN_SIGNATURES)) {
3451
+ const installed = await Promise.any(
3452
+ signatures.map(async (sig) => {
3453
+ if (await fs9.pathExists(path4.join(cwd, sig))) return true;
3454
+ throw new Error("not found");
3455
+ })
3456
+ ).catch(() => false);
3457
+ result.details[key] = { installed, signatures };
3458
+ if (installed) {
3459
+ result.installedPlugins.push(key);
3460
+ } else {
3461
+ result.missingPlugins.push(key);
3462
+ }
3463
+ }
3464
+ const hasAnyContent = result.installedPlugins.length > 0 || await fs9.pathExists(path4.join(cwd, "package.json")) || await fs9.pathExists(path4.join(cwd, "src")) || await fs9.pathExists(path4.join(cwd, "README.md"));
3465
+ result.isNewProject = !hasAnyContent;
3466
+ return result;
3467
+ }
3468
+ function getPluginsForMode(mode) {
3469
+ return Object.entries(PLUGIN_META).filter(([, meta]) => meta.modes.includes(mode)).map(([key, meta]) => ({ key, ...meta }));
3470
+ }
3471
+ function getBasePluginsForMode(mode) {
3472
+ return Object.entries(PLUGIN_META).filter(([, meta]) => meta.modes.includes(mode) && meta.required).map(([key]) => key);
3473
+ }
3474
+ function nextMode(current) {
3475
+ if (current === "prototype") return "poc";
3476
+ if (current === "poc") return "production";
3477
+ return null;
3478
+ }
3479
+ function pluginsAddedByPromotion(from, to) {
3480
+ const fromSet = new Set(getPluginsForMode(from).map((p) => p.key));
3481
+ return getPluginsForMode(to).map((p) => p.key).filter((k) => !fromSet.has(k));
3482
+ }
3483
+ function deriveEdges(installedPlugins) {
3484
+ const has = new Set(installedPlugins);
3485
+ const edges = [];
3486
+ if (has.has("harness") && has.has("openspec")) {
3487
+ edges.push({
3488
+ from: "harness",
3489
+ to: "openspec",
3490
+ via: "specFile",
3491
+ path: "docs/openspec.md",
3492
+ description: "harness\uAC00 openspec\uC758 API \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uBAA9\uB85D\uC744 \uC77D\uC5B4 \uAD6C\uD604 \uC77C\uCE58 \uAC80\uC99D"
3493
+ });
3494
+ }
3495
+ if (has.has("gstack") && has.has("openspec")) {
3496
+ edges.push({
3497
+ from: "gstack",
3498
+ to: "openspec",
3499
+ via: "endpoints",
3500
+ path: "docs/openspec.md",
3501
+ description: "gstack \uD14C\uC2A4\uD2B8\uAC00 openspec\uC758 \uAC01 \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uC5D0 \uB300\uD55C \uD14C\uC2A4\uD2B8 \uC874\uC7AC\uB97C \uC694\uAD6C"
3502
+ });
3503
+ }
3504
+ if (has.has("omc") && has.has("openspec")) {
3505
+ edges.push({
3506
+ from: "omc",
3507
+ to: "openspec",
3508
+ via: "domain",
3509
+ path: ".pai/omc.md",
3510
+ description: "omc\uC758 \uB3C4\uBA54\uC778 \uAC1D\uCCB4\uAC00 openspec\uC758 API \uC694\uCCAD/\uC751\uB2F5 \uBAA8\uB378\uACFC \uC815\uD569"
3511
+ });
3512
+ }
3513
+ if (has.has("harness") && has.has("omc")) {
3514
+ edges.push({
3515
+ from: "harness",
3516
+ to: "omc",
3517
+ via: "businessRules",
3518
+ path: ".pai/omc.md",
3519
+ description: "harness\uAC00 omc\uC758 \uBE44\uC988\uB2C8\uC2A4 \uADDC\uCE59\uC744 \uAC80\uC99D \uADDC\uCE59\uC73C\uB85C \uCC38\uC870"
3520
+ });
3521
+ }
3522
+ return edges;
3523
+ }
3524
+ var PLUGIN_SIGNATURES, PLUGIN_META;
3525
+ var init_detector = __esm({
3526
+ "src/core/detector.ts"() {
3527
+ "use strict";
3528
+ init_config();
3529
+ PLUGIN_SIGNATURES = {
3530
+ github: [".git", ".github"],
3531
+ vercel: [".vercel", "vercel.json"],
3532
+ supabase: ["supabase", ".supabase"],
3533
+ openspec: ["openspec.md", "docs/openspec.md", ".pai/openspec.md"],
3534
+ omc: [".pai/omc.md", "omc.config.js", "omc.config.json"],
3535
+ gstack: [".pai/gstack.json", "gstack.config.js"],
3536
+ roboco: [".pai/roboco.json", "roboco.config.js", ".roboco"],
3537
+ harness: [".pai/harness.json", "harness.config.js", ".harness"]
3538
+ };
3539
+ PLUGIN_META = {
3540
+ github: {
3541
+ label: "GitHub \uB808\uD3EC & \uD3F4\uB354 \uAD6C\uC870",
3542
+ description: "\uB808\uD3EC \uCD08\uAE30\uD654, .gitignore, \uAE30\uBCF8 \uBE0C\uB79C\uCE58 \uC124\uC815",
3543
+ modes: ["prototype", "poc", "production"],
3544
+ required: true
3545
+ },
3546
+ openspec: {
3547
+ label: "OpenSpec (PRD \uC124\uACC4)",
3548
+ description: "AI \uAE30\uBC18 PRD \uC0DD\uC131 \uBC0F \uC2A4\uD399 \uBB38\uC11C\uD654",
3549
+ modes: ["prototype", "poc", "production"],
3550
+ required: true
3551
+ },
3552
+ roboco: {
3553
+ label: "roboco (AI \uC9C4\uB2E8)",
3554
+ description: "\uC124\uCE58 \uC0C1\uD0DC \uD3C9\uAC00 \uBC0F AI \uC900\uBE44\uB3C4 \uB9AC\uD3EC\uD2B8 \uC0DD\uC131",
3555
+ modes: ["prototype", "poc", "production"],
3556
+ required: false,
3557
+ url: "https://github.com/SoInKyu/roboco-cli"
3558
+ },
3559
+ omc: {
3560
+ label: "OMC (oh-my-claudecode)",
3561
+ description: "\uAC1D\uCCB4 \uBAA8\uB378 \uCEE8\uD14D\uC2A4\uD2B8 + Claude Code \uBA40\uD2F0 \uC5D0\uC774\uC804\uD2B8 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158",
3562
+ modes: ["poc", "production"],
3563
+ required: false,
3564
+ url: "https://github.com/SoInKyu/oh-my-claudecode"
3565
+ },
3566
+ vercel: {
3567
+ label: "Vercel \uBC30\uD3EC \uC5F0\uB3D9",
3568
+ description: "\uC790\uB3D9 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uBC0F Preview URL \uC124\uC815",
3569
+ modes: ["poc", "production"],
3570
+ required: false
3571
+ },
3572
+ gstack: {
3573
+ label: "gstack (QA / \uD488\uC9C8\uAD00\uB9AC)",
3574
+ description: "\uD14C\uC2A4\uD2B8 \uC790\uB3D9\uD654 \uBC0F \uD488\uC9C8 \uAE30\uC900 \uC124\uC815",
3575
+ modes: ["production"],
3576
+ required: false,
3577
+ url: "https://github.com/SoInKyu/gstack"
3578
+ },
3579
+ harness: {
3580
+ label: "Harness Engineering (\uAC80\uC99D \uC790\uB3D9\uD654)",
3581
+ description: "\uC124\uACC4(OpenSpec)\uC640 \uAD6C\uD604 \uC77C\uCE58 \uC5EC\uBD80 \uC790\uB3D9 \uCCB4\uD06C",
3582
+ modes: ["production"],
3583
+ required: false
3584
+ },
3585
+ supabase: {
3586
+ label: "Supabase (DB & Auth)",
3587
+ description: "\uB370\uC774\uD130\uBCA0\uC774\uC2A4, \uC778\uC99D, API \uD0A4 \uC790\uB3D9 \uC5F0\uB3D9",
3588
+ modes: ["production"],
3589
+ required: false
3590
+ }
3591
+ };
3592
+ }
3593
+ });
3594
+
3184
3595
  // src/stages/environment/doctor.ts
3185
3596
  var doctor_exports = {};
3186
3597
  __export(doctor_exports, {
@@ -3188,7 +3599,7 @@ __export(doctor_exports, {
3188
3599
  });
3189
3600
  import { join as join6 } from "path";
3190
3601
  import { homedir } from "os";
3191
- import fs9 from "fs-extra";
3602
+ import fs10 from "fs-extra";
3192
3603
  async function runDoctor() {
3193
3604
  section("PAI Doctor \u2014 \uD658\uACBD \uC9C4\uB2E8");
3194
3605
  const checks = [];
@@ -3208,7 +3619,7 @@ async function runDoctor() {
3208
3619
  fix: claudeCheck.ok ? void 0 : "npm install -g @anthropic-ai/claude-code"
3209
3620
  });
3210
3621
  const globalConfigPath = join6(homedir(), ".pai", "config.json");
3211
- const hasGlobalConfig = await fs9.pathExists(globalConfigPath);
3622
+ const hasGlobalConfig = await fs10.pathExists(globalConfigPath);
3212
3623
  checks.push({
3213
3624
  label: "\uAE00\uB85C\uBC8C \uC124\uC815",
3214
3625
  ok: true,
@@ -3220,46 +3631,348 @@ async function runDoctor() {
3220
3631
  hasSdk = true;
3221
3632
  } catch {
3222
3633
  }
3223
- checks.push({
3224
- label: "Agent SDK",
3225
- ok: true,
3226
- // optional이므로 항상 OK
3227
- detail: hasSdk ? "\uC124\uCE58\uB428 (AI \uAE30\uB2A5 \uD65C\uC131\uD654)" : "\uBBF8\uC124\uCE58 (\uC815\uC801 \uBD84\uC11D \uBAA8\uB4DC)"
3228
- });
3229
- console.log("");
3230
- let passed = 0;
3231
- for (const check of checks) {
3232
- const icon = check.ok ? "\u2713" : "\u2717";
3233
- const pad = " ".repeat(Math.max(1, 20 - check.label.length));
3234
- if (check.ok) {
3235
- success(`${icon} ${check.label}${pad}${check.detail}`);
3236
- passed++;
3237
- } else {
3238
- error(`${icon} ${check.label}${pad}${check.detail}`);
3239
- if (check.fix) {
3240
- info(` \u2192 ${check.fix}`);
3634
+ checks.push({
3635
+ label: "Agent SDK",
3636
+ ok: true,
3637
+ // optional이므로 항상 OK
3638
+ detail: hasSdk ? "\uC124\uCE58\uB428 (AI \uAE30\uB2A5 \uD65C\uC131\uD654)" : "\uBBF8\uC124\uCE58 (\uC815\uC801 \uBD84\uC11D \uBAA8\uB4DC)"
3639
+ });
3640
+ console.log("");
3641
+ let passed = 0;
3642
+ for (const check of checks) {
3643
+ const icon = check.ok ? "\u2713" : "\u2717";
3644
+ const pad = " ".repeat(Math.max(1, 20 - check.label.length));
3645
+ if (check.ok) {
3646
+ success(`${icon} ${check.label}${pad}${check.detail}`);
3647
+ passed++;
3648
+ } else {
3649
+ error(`${icon} ${check.label}${pad}${check.detail}`);
3650
+ if (check.fix) {
3651
+ info(` \u2192 ${check.fix}`);
3652
+ }
3653
+ }
3654
+ }
3655
+ console.log("");
3656
+ info(`${passed}/${checks.length} \uD56D\uBAA9 \uD1B5\uACFC`);
3657
+ if (passed < checks.length) {
3658
+ process.exitCode = 1;
3659
+ }
3660
+ }
3661
+ async function checkCommand(cmd, args) {
3662
+ try {
3663
+ const { execa } = await import("execa");
3664
+ const { stdout } = await execa(cmd, args, { timeout: 1e4 });
3665
+ return { ok: true, detail: stdout.trim().split("\n")[0] ?? "ok" };
3666
+ } catch {
3667
+ return { ok: false, detail: "not found" };
3668
+ }
3669
+ }
3670
+ var init_doctor = __esm({
3671
+ "src/stages/environment/doctor.ts"() {
3672
+ "use strict";
3673
+ init_ui();
3674
+ }
3675
+ });
3676
+
3677
+ // src/utils/github-fetch.ts
3678
+ import path5 from "path";
3679
+ import fs11 from "fs-extra";
3680
+ async function httpGet(url, timeoutMs, accept) {
3681
+ const controller = new AbortController();
3682
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
3683
+ try {
3684
+ return await fetch(url, {
3685
+ signal: controller.signal,
3686
+ headers: { "Accept": accept, "User-Agent": "pai-zero" }
3687
+ });
3688
+ } finally {
3689
+ clearTimeout(timer);
3690
+ }
3691
+ }
3692
+ async function listDir(repo, ref, dirPath, timeoutMs) {
3693
+ const url = `https://api.github.com/repos/${repo}/contents/${encodeURI(dirPath)}?ref=${encodeURIComponent(ref)}`;
3694
+ const res = await httpGet(url, timeoutMs, "application/vnd.github+json");
3695
+ if (!res.ok) {
3696
+ throw new Error(`GitHub API ${res.status} ${res.statusText} (${url})`);
3697
+ }
3698
+ const data = await res.json();
3699
+ if (!Array.isArray(data)) {
3700
+ throw new Error(`Expected directory, got single entry at ${dirPath}`);
3701
+ }
3702
+ return data;
3703
+ }
3704
+ async function downloadFile(downloadUrl, destPath, timeoutMs) {
3705
+ const res = await httpGet(downloadUrl, timeoutMs, "*/*");
3706
+ if (!res.ok) {
3707
+ throw new Error(`Download failed ${res.status} ${res.statusText} (${downloadUrl})`);
3708
+ }
3709
+ const buf = Buffer.from(await res.arrayBuffer());
3710
+ await fs11.ensureDir(path5.dirname(destPath));
3711
+ await fs11.writeFile(destPath, buf);
3712
+ }
3713
+ async function fetchGithubDir(opts) {
3714
+ const {
3715
+ repo,
3716
+ ref = "main",
3717
+ srcPath,
3718
+ destDir,
3719
+ overwrite = false,
3720
+ timeoutMs = 1e4
3721
+ } = opts;
3722
+ const result = { written: [], skipped: [], errors: [] };
3723
+ async function walk(currentSrc, currentDest) {
3724
+ let entries;
3725
+ try {
3726
+ entries = await listDir(repo, ref, currentSrc, timeoutMs);
3727
+ } catch (err) {
3728
+ const msg = err instanceof Error ? err.message : String(err);
3729
+ result.errors.push({ path: currentSrc, error: msg });
3730
+ return;
3731
+ }
3732
+ for (const entry of entries) {
3733
+ const relName = entry.name;
3734
+ if (entry.type === "dir") {
3735
+ await walk(`${currentSrc}/${relName}`, path5.join(currentDest, relName));
3736
+ } else if (entry.type === "file") {
3737
+ const destFile = path5.join(currentDest, relName);
3738
+ if (!overwrite && await fs11.pathExists(destFile)) {
3739
+ result.skipped.push(destFile);
3740
+ continue;
3741
+ }
3742
+ if (!entry.download_url) {
3743
+ result.errors.push({ path: entry.path, error: "no download_url" });
3744
+ continue;
3745
+ }
3746
+ try {
3747
+ await downloadFile(entry.download_url, destFile, timeoutMs);
3748
+ result.written.push(destFile);
3749
+ } catch (err) {
3750
+ const msg = err instanceof Error ? err.message : String(err);
3751
+ result.errors.push({ path: entry.path, error: msg });
3752
+ }
3753
+ }
3754
+ }
3755
+ }
3756
+ await walk(srcPath, destDir);
3757
+ return result;
3758
+ }
3759
+ var init_github_fetch = __esm({
3760
+ "src/utils/github-fetch.ts"() {
3761
+ "use strict";
3762
+ }
3763
+ });
3764
+
3765
+ // src/cli/commands/fetch.cmd.ts
3766
+ var fetch_cmd_exports = {};
3767
+ __export(fetch_cmd_exports, {
3768
+ fetchCommand: () => fetchCommand
3769
+ });
3770
+ import path6 from "path";
3771
+ import fs12 from "fs-extra";
3772
+ import chalk4 from "chalk";
3773
+ async function fetchCommand(cwd, recipeKey, options) {
3774
+ if (options.list) {
3775
+ section("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uB808\uC2DC\uD53C");
3776
+ for (const key of listRecipeKeys()) {
3777
+ const r = RECIPES[key];
3778
+ console.log(` ${colors.accent(key.padEnd(14))} ${r.label}`);
3779
+ console.log(` ${colors.dim(" " + r.description)}`);
3780
+ console.log(` ${colors.dim(" \uC18C\uC2A4: github.com/" + r.source.repo + "/tree/" + r.source.ref + "/" + r.source.path)}`);
3781
+ console.log("");
3782
+ }
3783
+ hint("\uC0AC\uC6A9: pai fetch <key> \uB610\uB294 pai fetch --all");
3784
+ return;
3785
+ }
3786
+ let keys;
3787
+ if (options.all) {
3788
+ keys = listRecipeKeys();
3789
+ } else {
3790
+ if (!recipeKey) {
3791
+ error("\uB808\uC2DC\uD53C \uD0A4\uB97C \uC9C0\uC815\uD558\uC138\uC694.");
3792
+ hint(`\uC0AC\uC6A9 \uAC00\uB2A5: ${listRecipeKeys().join(", ")}`);
3793
+ hint("\uBAA9\uB85D: pai fetch --list");
3794
+ process.exitCode = 1;
3795
+ return;
3796
+ }
3797
+ if (!getRecipe(recipeKey)) {
3798
+ error(`\uC54C \uC218 \uC5C6\uB294 \uB808\uC2DC\uD53C: ${recipeKey}`);
3799
+ hint(`\uC0AC\uC6A9 \uAC00\uB2A5: ${listRecipeKeys().join(", ")}`);
3800
+ process.exitCode = 1;
3801
+ return;
3802
+ }
3803
+ keys = [recipeKey.toLowerCase()];
3804
+ }
3805
+ section("\uB808\uC2DC\uD53C \uB2E4\uC6B4\uB85C\uB4DC");
3806
+ const installed = [];
3807
+ for (const key of keys) {
3808
+ const recipe = RECIPES[key];
3809
+ const targetDir = path6.join(cwd, recipe.target);
3810
+ console.log("");
3811
+ console.log(` ${colors.accent(key)} \u2014 ${recipe.label}`);
3812
+ const result = await withSpinner(`\uB2E4\uC6B4\uB85C\uB4DC \uC911...`, async () => {
3813
+ return fetchGithubDir({
3814
+ repo: recipe.source.repo,
3815
+ ref: recipe.source.ref,
3816
+ srcPath: recipe.source.path,
3817
+ destDir: targetDir,
3818
+ overwrite: options.overwrite ?? false
3819
+ });
3820
+ });
3821
+ if (result.errors.length > 0) {
3822
+ error(`\uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328: ${result.errors[0].error}`);
3823
+ for (const e of result.errors.slice(1)) {
3824
+ console.log(chalk4.gray(` ${e.path}: ${e.error}`));
3825
+ }
3826
+ continue;
3827
+ }
3828
+ if (result.written.length > 0) {
3829
+ success(`${result.written.length}\uAC1C \uD30C\uC77C \uC800\uC7A5`);
3830
+ for (const f of result.written) {
3831
+ console.log(chalk4.gray(` ${path6.relative(cwd, f)}`));
3241
3832
  }
3242
3833
  }
3834
+ if (result.skipped.length > 0) {
3835
+ info(`${result.skipped.length}\uAC1C \uD30C\uC77C \uAC74\uB108\uB700 (\uC774\uBBF8 \uC874\uC7AC \u2014 \uB36E\uC5B4\uC4F0\uAE30: --overwrite)`);
3836
+ }
3837
+ await appendEnvKeys(cwd, recipe);
3838
+ installed.push(key);
3839
+ }
3840
+ if (installed.length === 0) {
3841
+ return;
3842
+ }
3843
+ await upsertRecipesSkill(cwd, installed);
3844
+ await upsertClaudeMdBlock(cwd, installed);
3845
+ console.log("");
3846
+ success(`\uB808\uC2DC\uD53C ${installed.length}\uAC1C \uC124\uCE58 \uC644\uB8CC: ${installed.join(", ")}`);
3847
+ console.log("");
3848
+ console.log(colors.accent(" \uB2E4\uC74C \uB2E8\uACC4"));
3849
+ 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"));
3850
+ console.log(` ${colors.success("1.")} ${colors.dim(".env.local \uD30C\uC77C\uC744 \uC5F4\uC5B4 \uD658\uACBD\uBCC0\uC218 \uAC12\uC744 \uCC44\uC6B0\uC138\uC694")}`);
3851
+ for (const key of installed) {
3852
+ const recipe = RECIPES[key];
3853
+ for (const ek of recipe.envKeys) {
3854
+ console.log(` ${chalk4.cyan(ek.key.padEnd(22))} ${colors.dim(ek.hint ?? "")}`);
3855
+ }
3243
3856
  }
3244
3857
  console.log("");
3245
- info(`${passed}/${checks.length} \uD56D\uBAA9 \uD1B5\uACFC`);
3246
- if (passed < checks.length) {
3247
- process.exitCode = 1;
3858
+ console.log(` ${colors.success("2.")} ${colors.dim("Claude Code\uC5D0\uC11C \uAD00\uB828 \uAE30\uB2A5 \uAD6C\uD604 \uC2DC \uC790\uB3D9\uC73C\uB85C \uB808\uC2DC\uD53C \uBB38\uC11C\uB97C \uCC38\uC870\uD569\uB2C8\uB2E4")}`);
3859
+ for (const key of installed) {
3860
+ const recipe = RECIPES[key];
3861
+ console.log(` ${chalk4.cyan(recipe.target + "/")}`);
3248
3862
  }
3863
+ console.log("");
3249
3864
  }
3250
- async function checkCommand(cmd, args) {
3251
- try {
3252
- const { execa } = await import("execa");
3253
- const { stdout } = await execa(cmd, args, { timeout: 1e4 });
3254
- return { ok: true, detail: stdout.trim().split("\n")[0] ?? "ok" };
3255
- } catch {
3256
- return { ok: false, detail: "not found" };
3865
+ async function appendEnvKeys(cwd, recipe) {
3866
+ if (recipe.envKeys.length === 0) return;
3867
+ const envPath = path6.join(cwd, ".env.local");
3868
+ let content = "";
3869
+ if (await fs12.pathExists(envPath)) {
3870
+ content = await fs12.readFile(envPath, "utf8");
3871
+ }
3872
+ const missingKeys = recipe.envKeys.filter((ek) => !content.includes(`${ek.key}=`));
3873
+ if (missingKeys.length === 0) return;
3874
+ const lines = [];
3875
+ if (content.length > 0 && !content.endsWith("\n")) lines.push("");
3876
+ lines.push("");
3877
+ lines.push(`# \u2500\u2500 ${recipe.label} \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`);
3878
+ lines.push(`# \uAC00\uC774\uB4DC: ${recipe.target}/guideline.md`);
3879
+ for (const ek of missingKeys) {
3880
+ if (ek.hint) lines.push(`# ${ek.hint}`);
3881
+ lines.push(`${ek.key}=${ek.default ?? ""}`);
3882
+ }
3883
+ await fs12.ensureFile(envPath);
3884
+ await fs12.appendFile(envPath, lines.join("\n") + "\n");
3885
+ }
3886
+ async function upsertRecipesSkill(cwd, installedKeys) {
3887
+ const skillDir = path6.join(cwd, ".claude", "skills", "recipes");
3888
+ await fs12.ensureDir(skillDir);
3889
+ const skillPath = path6.join(skillDir, "SKILL.md");
3890
+ const recipes = installedKeys.map((k) => RECIPES[k]);
3891
+ const triggers = recipes.map((r) => r.skillDescription ?? `${r.label} \uAD00\uB828 \uAE30\uB2A5 \uAD6C\uD604 \uC2DC ${r.target}/ \uCC38\uC870`).join("\n- ");
3892
+ const body = [
3893
+ "---",
3894
+ "name: recipes",
3895
+ `description: "\uC0AC\uB0B4 \uC2DC\uC2A4\uD15C \uC5F0\uB3D9 \uB808\uC2DC\uD53C \u2014 ${recipes.map((r) => r.label).join(", ")}"`,
3896
+ "---",
3897
+ "",
3898
+ "# \uC0AC\uB0B4 \uC2DC\uC2A4\uD15C \uC5F0\uB3D9 \uB808\uC2DC\uD53C",
3899
+ "",
3900
+ "\uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0\uB294 \uB2E4\uC74C \uC0AC\uB0B4 \uC5F0\uB3D9 \uB808\uC2DC\uD53C\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
3901
+ "\uAD00\uB828 \uC694\uCCAD\uC744 \uBC1B\uC73C\uBA74 \uD574\uB2F9 \uACBD\uB85C\uC758 `guideline.md`\uB97C **\uBC18\uB4DC\uC2DC \uBA3C\uC800 \uC77D\uACE0**",
3902
+ "\uC9C0\uCE68\uC5D0 \uB9DE\uAC8C \uAD6C\uD604\uD558\uC138\uC694.",
3903
+ "",
3904
+ "## \uD2B8\uB9AC\uAC70",
3905
+ "",
3906
+ "- " + triggers,
3907
+ "",
3908
+ "## \uC124\uCE58\uB41C \uB808\uC2DC\uD53C",
3909
+ "",
3910
+ ...recipes.map((r) => [
3911
+ `### ${r.label}`,
3912
+ "",
3913
+ `- \uACBD\uB85C: \`${r.target}/\``,
3914
+ `- \uC8FC\uC694 \uBB38\uC11C: \`${r.target}/guideline.md\``,
3915
+ `- \uD658\uACBD\uBCC0\uC218: ${r.envKeys.map((e) => "`" + e.key + "`").join(", ")}`,
3916
+ ""
3917
+ ].join("\n")),
3918
+ "",
3919
+ "## \uC791\uC5C5 \uC21C\uC11C",
3920
+ "",
3921
+ "1. \uC0AC\uC6A9\uC790\uC758 \uC694\uCCAD\uC774 \uC704 \uD2B8\uB9AC\uAC70 \uC911 \uD558\uB098\uC5D0 \uD574\uB2F9\uD558\uB294\uC9C0 \uD310\uB2E8",
3922
+ "2. \uD574\uB2F9 \uB808\uC2DC\uD53C\uC758 `guideline.md`\uB97C Read \uB3C4\uAD6C\uB85C \uC77D\uAE30",
3923
+ "3. \uC0D8\uD50C \uCF54\uB4DC(`*.html`, `*.ts` \uB4F1)\uAC00 \uC788\uC73C\uBA74 \uD568\uAED8 \uD655\uC778",
3924
+ "4. `.env.local`\uC5D0 \uAD00\uB828 \uD658\uACBD\uBCC0\uC218\uAC00 \uCC44\uC6CC\uC838 \uC788\uB294\uC9C0 \uD655\uC778",
3925
+ "5. \uAC00\uC774\uB4DC \uC21C\uC11C\uC5D0 \uB530\uB77C \uAD6C\uD604 \u2014 \uB808\uC2DC\uD53C \uADDC\uCE59\uC744 \uC808\uB300 \uC6B0\uD68C\uD558\uC9C0 \uB9D0 \uAC83",
3926
+ ""
3927
+ ].join("\n");
3928
+ await fs12.writeFile(skillPath, body);
3929
+ }
3930
+ async function upsertClaudeMdBlock(cwd, installedKeys) {
3931
+ const claudeMdPath = path6.join(cwd, "CLAUDE.md");
3932
+ const BLOCK_START = "<!-- pai:recipes:start -->";
3933
+ const BLOCK_END = "<!-- pai:recipes:end -->";
3934
+ const lines = [];
3935
+ lines.push(BLOCK_START);
3936
+ lines.push("## \uC0AC\uB0B4 \uC2DC\uC2A4\uD15C \uC5F0\uB3D9");
3937
+ lines.push("");
3938
+ lines.push("\uB2E4\uC74C \uB808\uC2DC\uD53C\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uAD00\uB828 \uAE30\uB2A5 \uAD6C\uD604 \uC2DC **\uBC18\uB4DC\uC2DC** \uD574\uB2F9 \uACBD\uB85C\uC758");
3939
+ lines.push("`guideline.md`\uB97C \uBA3C\uC800 \uC77D\uACE0 \uC9C0\uCE68\uC744 \uB530\uB974\uC138\uC694:");
3940
+ lines.push("");
3941
+ for (const key of installedKeys) {
3942
+ const r = RECIPES[key];
3943
+ lines.push(`- **${r.label}** \u2014 \`${r.target}/\``);
3944
+ lines.push(` \uD658\uACBD\uBCC0\uC218: ${r.envKeys.map((e) => "`" + e.key + "`").join(", ")}`);
3945
+ }
3946
+ lines.push("");
3947
+ lines.push("\uD658\uACBD\uBCC0\uC218 \uAC12\uC740 `.env.local`\uC5D0 \uCC44\uC6CC\uC838 \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4.");
3948
+ lines.push(BLOCK_END);
3949
+ const block = lines.join("\n");
3950
+ let content = "";
3951
+ if (await fs12.pathExists(claudeMdPath)) {
3952
+ content = await fs12.readFile(claudeMdPath, "utf8");
3953
+ }
3954
+ const startIdx = content.indexOf(BLOCK_START);
3955
+ const endIdx = content.indexOf(BLOCK_END);
3956
+ if (startIdx >= 0 && endIdx > startIdx) {
3957
+ const before = content.slice(0, startIdx);
3958
+ const after = content.slice(endIdx + BLOCK_END.length);
3959
+ content = before + block + after;
3960
+ } else {
3961
+ if (content.length > 0 && !content.endsWith("\n")) content += "\n";
3962
+ if (content.length > 0) content += "\n";
3963
+ content += block + "\n";
3257
3964
  }
3965
+ await fs12.ensureFile(claudeMdPath);
3966
+ await fs12.writeFile(claudeMdPath, content);
3258
3967
  }
3259
- var init_doctor = __esm({
3260
- "src/stages/environment/doctor.ts"() {
3968
+ var init_fetch_cmd = __esm({
3969
+ "src/cli/commands/fetch.cmd.ts"() {
3261
3970
  "use strict";
3262
3971
  init_ui();
3972
+ init_logger();
3973
+ init_recipes();
3974
+ init_github_fetch();
3975
+ init_progress();
3263
3976
  }
3264
3977
  });
3265
3978
 
@@ -3274,6 +3987,7 @@ var init_environment = __esm({
3274
3987
  init_installer();
3275
3988
  init_registry();
3276
3989
  init_claude_commands();
3990
+ init_detector();
3277
3991
  init_config();
3278
3992
  init_progress();
3279
3993
  init_ui();
@@ -3331,6 +4045,7 @@ var init_environment = __esm({
3331
4045
  PAI_PROJECT_NAME: interview.projectName,
3332
4046
  PAI_MODE: interview.mode
3333
4047
  },
4048
+ analysis,
3334
4049
  mcp: interview.mcp
3335
4050
  };
3336
4051
  if (interview.authMethods.includes("custom")) {
@@ -3378,10 +4093,24 @@ var init_environment = __esm({
3378
4093
  console.log("");
3379
4094
  const installResults = await installTools(interview.tools, input.cwd);
3380
4095
  printInstallReport(installResults, interview.tools);
4096
+ if (interview.recipes && interview.recipes.length > 0) {
4097
+ console.log("");
4098
+ const { fetchCommand: fetchCommand2 } = await Promise.resolve().then(() => (init_fetch_cmd(), fetch_cmd_exports));
4099
+ for (const recipeKey of interview.recipes) {
4100
+ try {
4101
+ await fetchCommand2(input.cwd, recipeKey, {});
4102
+ } catch (err) {
4103
+ const msg = err instanceof Error ? err.message : String(err);
4104
+ warn(`\uB808\uC2DC\uD53C '${recipeKey}' \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328 \u2014 ${msg}`);
4105
+ hint(`\uB098\uC911\uC5D0 \uC218\uB3D9: pai fetch ${recipeKey}`);
4106
+ }
4107
+ }
4108
+ }
3381
4109
  console.log("");
3382
4110
  await withSpinner("\uC124\uC815 \uC800\uC7A5 \uC911...", async () => {
3383
4111
  const config = createDefaultConfig(interview.projectName, interview.mode);
3384
4112
  config.plugins = pluginKeys;
4113
+ config.edges = deriveEdges(pluginKeys);
3385
4114
  if (interview.mcp) config.mcp = interview.mcp;
3386
4115
  await saveConfig(input.cwd, config);
3387
4116
  await sleep(300);
@@ -3411,141 +4140,6 @@ var init_environment = __esm({
3411
4140
  }
3412
4141
  });
3413
4142
 
3414
- // src/core/detector.ts
3415
- var detector_exports = {};
3416
- __export(detector_exports, {
3417
- PLUGIN_META: () => PLUGIN_META,
3418
- PLUGIN_SIGNATURES: () => PLUGIN_SIGNATURES,
3419
- getBasePluginsForMode: () => getBasePluginsForMode,
3420
- getPluginsForMode: () => getPluginsForMode,
3421
- nextMode: () => nextMode,
3422
- pluginsAddedByPromotion: () => pluginsAddedByPromotion,
3423
- scanProjectState: () => scanProjectState
3424
- });
3425
- import path4 from "path";
3426
- import fs10 from "fs-extra";
3427
- async function scanProjectState(cwd) {
3428
- const result = {
3429
- isNewProject: true,
3430
- hasPaiConfig: false,
3431
- projectMode: null,
3432
- installedPlugins: [],
3433
- missingPlugins: [],
3434
- details: {}
3435
- };
3436
- const paiConfigPath = path4.join(cwd, ".pai", "config.json");
3437
- if (await fs10.pathExists(paiConfigPath)) {
3438
- result.hasPaiConfig = true;
3439
- try {
3440
- const config = await fs10.readJson(paiConfigPath);
3441
- result.projectMode = config.mode != null ? normalizeMode(config.mode) : null;
3442
- } catch {
3443
- }
3444
- }
3445
- for (const [key, signatures] of Object.entries(PLUGIN_SIGNATURES)) {
3446
- const installed = await Promise.any(
3447
- signatures.map(async (sig) => {
3448
- if (await fs10.pathExists(path4.join(cwd, sig))) return true;
3449
- throw new Error("not found");
3450
- })
3451
- ).catch(() => false);
3452
- result.details[key] = { installed, signatures };
3453
- if (installed) {
3454
- result.installedPlugins.push(key);
3455
- } else {
3456
- result.missingPlugins.push(key);
3457
- }
3458
- }
3459
- const hasAnyContent = result.installedPlugins.length > 0 || await fs10.pathExists(path4.join(cwd, "package.json")) || await fs10.pathExists(path4.join(cwd, "src")) || await fs10.pathExists(path4.join(cwd, "README.md"));
3460
- result.isNewProject = !hasAnyContent;
3461
- return result;
3462
- }
3463
- function getPluginsForMode(mode) {
3464
- return Object.entries(PLUGIN_META).filter(([, meta]) => meta.modes.includes(mode)).map(([key, meta]) => ({ key, ...meta }));
3465
- }
3466
- function getBasePluginsForMode(mode) {
3467
- return Object.entries(PLUGIN_META).filter(([, meta]) => meta.modes.includes(mode) && meta.required).map(([key]) => key);
3468
- }
3469
- function nextMode(current) {
3470
- if (current === "prototype") return "poc";
3471
- if (current === "poc") return "production";
3472
- return null;
3473
- }
3474
- function pluginsAddedByPromotion(from, to) {
3475
- const fromSet = new Set(getPluginsForMode(from).map((p) => p.key));
3476
- return getPluginsForMode(to).map((p) => p.key).filter((k) => !fromSet.has(k));
3477
- }
3478
- var PLUGIN_SIGNATURES, PLUGIN_META;
3479
- var init_detector = __esm({
3480
- "src/core/detector.ts"() {
3481
- "use strict";
3482
- init_config();
3483
- PLUGIN_SIGNATURES = {
3484
- github: [".git", ".github"],
3485
- vercel: [".vercel", "vercel.json"],
3486
- supabase: ["supabase", ".supabase"],
3487
- openspec: ["openspec.md", "docs/openspec.md", ".pai/openspec.md"],
3488
- omc: [".pai/omc.md", "omc.config.js", "omc.config.json"],
3489
- gstack: [".pai/gstack.json", "gstack.config.js"],
3490
- roboco: [".pai/roboco.json", "roboco.config.js", ".roboco"],
3491
- harness: [".pai/harness.json", "harness.config.js", ".harness"]
3492
- };
3493
- PLUGIN_META = {
3494
- github: {
3495
- label: "GitHub \uB808\uD3EC & \uD3F4\uB354 \uAD6C\uC870",
3496
- description: "\uB808\uD3EC \uCD08\uAE30\uD654, .gitignore, \uAE30\uBCF8 \uBE0C\uB79C\uCE58 \uC124\uC815",
3497
- modes: ["prototype", "poc", "production"],
3498
- required: true
3499
- },
3500
- openspec: {
3501
- label: "OpenSpec (PRD \uC124\uACC4)",
3502
- description: "AI \uAE30\uBC18 PRD \uC0DD\uC131 \uBC0F \uC2A4\uD399 \uBB38\uC11C\uD654",
3503
- modes: ["prototype", "poc", "production"],
3504
- required: true
3505
- },
3506
- roboco: {
3507
- label: "roboco (AI \uC9C4\uB2E8)",
3508
- description: "\uC124\uCE58 \uC0C1\uD0DC \uD3C9\uAC00 \uBC0F AI \uC900\uBE44\uB3C4 \uB9AC\uD3EC\uD2B8 \uC0DD\uC131",
3509
- modes: ["prototype", "poc", "production"],
3510
- required: false,
3511
- url: "https://github.com/SoInKyu/roboco-cli"
3512
- },
3513
- omc: {
3514
- label: "OMC (oh-my-claudecode)",
3515
- description: "\uAC1D\uCCB4 \uBAA8\uB378 \uCEE8\uD14D\uC2A4\uD2B8 + Claude Code \uBA40\uD2F0 \uC5D0\uC774\uC804\uD2B8 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158",
3516
- modes: ["poc", "production"],
3517
- required: false,
3518
- url: "https://github.com/SoInKyu/oh-my-claudecode"
3519
- },
3520
- vercel: {
3521
- label: "Vercel \uBC30\uD3EC \uC5F0\uB3D9",
3522
- description: "\uC790\uB3D9 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uBC0F Preview URL \uC124\uC815",
3523
- modes: ["poc", "production"],
3524
- required: false
3525
- },
3526
- gstack: {
3527
- label: "gstack (QA / \uD488\uC9C8\uAD00\uB9AC)",
3528
- description: "\uD14C\uC2A4\uD2B8 \uC790\uB3D9\uD654 \uBC0F \uD488\uC9C8 \uAE30\uC900 \uC124\uC815",
3529
- modes: ["production"],
3530
- required: false,
3531
- url: "https://github.com/SoInKyu/gstack"
3532
- },
3533
- harness: {
3534
- label: "Harness Engineering (\uAC80\uC99D \uC790\uB3D9\uD654)",
3535
- description: "\uC124\uACC4(OpenSpec)\uC640 \uAD6C\uD604 \uC77C\uCE58 \uC5EC\uBD80 \uC790\uB3D9 \uCCB4\uD06C",
3536
- modes: ["production"],
3537
- required: false
3538
- },
3539
- supabase: {
3540
- label: "Supabase (DB & Auth)",
3541
- description: "\uB370\uC774\uD130\uBCA0\uC774\uC2A4, \uC778\uC99D, API \uD0A4 \uC790\uB3D9 \uC5F0\uB3D9",
3542
- modes: ["production"],
3543
- required: false
3544
- }
3545
- };
3546
- }
3547
- });
3548
-
3549
4143
  // src/stages/evaluation/prompts/analyze.ts
3550
4144
  var analyze_exports = {};
3551
4145
  __export(analyze_exports, {
@@ -3603,7 +4197,7 @@ __export(analyzer_exports2, {
3603
4197
  analyzeRepository: () => analyzeRepository
3604
4198
  });
3605
4199
  import { join as join7 } from "path";
3606
- import fs11 from "fs-extra";
4200
+ import fs13 from "fs-extra";
3607
4201
  async function analyzeRepository(repoPath) {
3608
4202
  try {
3609
4203
  return await aiAnalysis(repoPath);
@@ -3664,14 +4258,14 @@ async function checkTestCoverage(repoPath) {
3664
4258
  ".nycrc"
3665
4259
  ];
3666
4260
  for (const f of testConfigs) {
3667
- const found = await fs11.pathExists(join7(repoPath, f));
4261
+ const found = await fs13.pathExists(join7(repoPath, f));
3668
4262
  findings.push({ item: f, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
3669
4263
  if (found) score += 20;
3670
4264
  }
3671
4265
  const testDirs = ["tests", "test", "__tests__", "spec"];
3672
4266
  let hasTestDir = false;
3673
4267
  for (const d of testDirs) {
3674
- if (await fs11.pathExists(join7(repoPath, d))) {
4268
+ if (await fs13.pathExists(join7(repoPath, d))) {
3675
4269
  findings.push({ item: d, found: true, details: "\uD14C\uC2A4\uD2B8 \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC" });
3676
4270
  hasTestDir = true;
3677
4271
  score += 30;
@@ -3694,8 +4288,8 @@ async function checkCiCd(repoPath) {
3694
4288
  { path: "Jenkinsfile", label: "Jenkins" },
3695
4289
  { path: ".circleci", label: "CircleCI" }
3696
4290
  ];
3697
- for (const { path: path6, label } of ciConfigs) {
3698
- const found = await fs11.pathExists(join7(repoPath, path6));
4291
+ for (const { path: path8, label } of ciConfigs) {
4292
+ const found = await fs13.pathExists(join7(repoPath, path8));
3699
4293
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
3700
4294
  if (found) score += 40;
3701
4295
  }
@@ -3713,8 +4307,8 @@ async function checkHooks(repoPath) {
3713
4307
  { path: "commitlint.config.js", label: "commitlint" },
3714
4308
  { path: ".claude/settings.json", label: "Claude Code settings" }
3715
4309
  ];
3716
- for (const { path: path6, label } of hookConfigs) {
3717
- const found = await fs11.pathExists(join7(repoPath, path6));
4310
+ for (const { path: path8, label } of hookConfigs) {
4311
+ const found = await fs13.pathExists(join7(repoPath, path8));
3718
4312
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
3719
4313
  if (found) score += 20;
3720
4314
  }
@@ -3731,8 +4325,8 @@ async function checkRepoStructure(repoPath) {
3731
4325
  { path: ".env.example", label: "\uD658\uACBD\uBCC0\uC218 \uC608\uC2DC" },
3732
4326
  { path: ".gitignore", label: ".gitignore" }
3733
4327
  ];
3734
- for (const { path: path6, label } of structureChecks) {
3735
- const found = await fs11.pathExists(join7(repoPath, path6));
4328
+ for (const { path: path8, label } of structureChecks) {
4329
+ const found = await fs13.pathExists(join7(repoPath, path8));
3736
4330
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
3737
4331
  if (found) score += 25;
3738
4332
  }
@@ -3748,8 +4342,8 @@ async function checkDocumentation(repoPath) {
3748
4342
  { path: "docs", label: "docs/ \uB514\uB809\uD1A0\uB9AC", points: 25 },
3749
4343
  { path: "docs/openspec.md", label: "OpenSpec PRD", points: 25 }
3750
4344
  ];
3751
- for (const { path: path6, label, points } of docChecks) {
3752
- const found = await fs11.pathExists(join7(repoPath, path6));
4345
+ for (const { path: path8, label, points } of docChecks) {
4346
+ const found = await fs13.pathExists(join7(repoPath, path8));
3753
4347
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
3754
4348
  if (found) score += points;
3755
4349
  }
@@ -3767,8 +4361,8 @@ async function checkHarnessEngineering(repoPath) {
3767
4361
  { path: ".claude/commands", label: ".claude/commands/", points: 10 },
3768
4362
  { path: ".pai/config.json", label: "PAI config", points: 10 }
3769
4363
  ];
3770
- for (const { path: path6, label, points } of harnessChecks) {
3771
- const found = await fs11.pathExists(join7(repoPath, path6));
4364
+ for (const { path: path8, label, points } of harnessChecks) {
4365
+ const found = await fs13.pathExists(join7(repoPath, path8));
3772
4366
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
3773
4367
  if (found) score += points;
3774
4368
  }
@@ -3902,41 +4496,41 @@ __export(reporter_exports, {
3902
4496
  printReport: () => printReport,
3903
4497
  printVerboseFindings: () => printVerboseFindings
3904
4498
  });
3905
- import chalk4 from "chalk";
4499
+ import chalk5 from "chalk";
3906
4500
  function printReport(result) {
3907
4501
  console.log("");
3908
- console.log(chalk4.hex("#7B93DB")(" PAI Evaluation Report"));
3909
- console.log(chalk4.gray(" \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\u2500\u2500\u2500\u2500"));
4502
+ console.log(chalk5.hex("#7B93DB")(" PAI Evaluation Report"));
4503
+ console.log(chalk5.gray(" \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\u2500\u2500\u2500\u2500"));
3910
4504
  console.log("");
3911
4505
  const gradeColor = GRADE_COLORS[result.totalGrade];
3912
4506
  console.log(` \uC885\uD569 \uC810\uC218: ${gradeColor(String(result.totalScore))} / 100 \uB4F1\uAE09: ${gradeColor(result.totalGrade)}`);
3913
4507
  if (result.penaltyApplied) {
3914
- console.log(chalk4.red(` \u26A0 ${result.penaltyReason}`));
4508
+ console.log(chalk5.red(` \u26A0 ${result.penaltyReason}`));
3915
4509
  }
3916
4510
  console.log("");
3917
- console.log(chalk4.gray(" \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4511
+ console.log(chalk5.gray(" \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
3918
4512
  for (const cat of result.categories) {
3919
4513
  const color = GRADE_COLORS[cat.grade];
3920
- const tierLabel = cat.tier === "must" ? chalk4.hex("#E06C75")("[\uD544\uC218]") : chalk4.gray("[\uC120\uD0DD]");
4514
+ const tierLabel = cat.tier === "must" ? chalk5.hex("#E06C75")("[\uD544\uC218]") : chalk5.gray("[\uC120\uD0DD]");
3921
4515
  const bar = renderBar(cat.score);
3922
4516
  console.log(` ${tierLabel} ${cat.name.padEnd(16)} ${bar} ${color(`${cat.score}`).padStart(12)} ${color(cat.grade)}`);
3923
4517
  }
3924
4518
  console.log("");
3925
- console.log(chalk4.gray(` ${result.summary}`));
4519
+ console.log(chalk5.gray(` ${result.summary}`));
3926
4520
  console.log("");
3927
4521
  }
3928
4522
  function printVerboseFindings(result) {
3929
4523
  for (const cat of result.categories) {
3930
4524
  console.log("");
3931
- console.log(chalk4.bold(` ${cat.name} (${cat.grade}, ${cat.score}\uC810)`));
4525
+ console.log(chalk5.bold(` ${cat.name} (${cat.grade}, ${cat.score}\uC810)`));
3932
4526
  for (const f of cat.rawFindings) {
3933
- const icon = f.found ? chalk4.green("\u2713") : chalk4.red("\u2717");
4527
+ const icon = f.found ? chalk5.green("\u2713") : chalk5.red("\u2717");
3934
4528
  console.log(` ${icon} ${f.item} \u2014 ${f.details}`);
3935
4529
  }
3936
4530
  for (const r of cat.recommendations) {
3937
- const severity = r.severity === "critical" ? chalk4.red("!") : r.severity === "warning" ? chalk4.yellow("!") : chalk4.gray("i");
4531
+ const severity = r.severity === "critical" ? chalk5.red("!") : r.severity === "warning" ? chalk5.yellow("!") : chalk5.gray("i");
3938
4532
  console.log(` ${severity} ${r.message}`);
3939
- console.log(chalk4.gray(` \u2192 ${r.action}`));
4533
+ console.log(chalk5.gray(` \u2192 ${r.action}`));
3940
4534
  }
3941
4535
  }
3942
4536
  }
@@ -3944,8 +4538,8 @@ function renderBar(score) {
3944
4538
  const width = 20;
3945
4539
  const filled = Math.round(score / 100 * width);
3946
4540
  const empty = width - filled;
3947
- const color = score >= 80 ? chalk4.hex("#6BCB77") : score >= 60 ? chalk4.hex("#E2B340") : chalk4.hex("#E06C75");
3948
- return color("\u2588".repeat(filled)) + chalk4.gray("\u2591".repeat(empty));
4541
+ const color = score >= 80 ? chalk5.hex("#6BCB77") : score >= 60 ? chalk5.hex("#E2B340") : chalk5.hex("#E06C75");
4542
+ return color("\u2588".repeat(filled)) + chalk5.gray("\u2591".repeat(empty));
3949
4543
  }
3950
4544
  function buildMarkdownReport(result) {
3951
4545
  const lines = [
@@ -4136,11 +4730,11 @@ var init_reporter = __esm({
4136
4730
  "src/stages/evaluation/reporter.ts"() {
4137
4731
  "use strict";
4138
4732
  GRADE_COLORS = {
4139
- A: chalk4.hex("#6BCB77"),
4140
- B: chalk4.hex("#7B93DB"),
4141
- C: chalk4.hex("#E2B340"),
4142
- D: chalk4.hex("#E06C75"),
4143
- F: chalk4.hex("#CC4444")
4733
+ A: chalk5.hex("#6BCB77"),
4734
+ B: chalk5.hex("#7B93DB"),
4735
+ C: chalk5.hex("#E2B340"),
4736
+ D: chalk5.hex("#E06C75"),
4737
+ F: chalk5.hex("#CC4444")
4144
4738
  };
4145
4739
  }
4146
4740
  });
@@ -4153,54 +4747,54 @@ __export(shell_cd_exports, {
4153
4747
  });
4154
4748
  import { join as join8 } from "path";
4155
4749
  import { homedir as homedir2 } from "os";
4156
- import fs12 from "fs-extra";
4750
+ import fs14 from "fs-extra";
4157
4751
  async function requestCdAfter(targetDir) {
4158
- await fs12.ensureDir(PAI_DIR);
4159
- await fs12.writeFile(CD_FILE, targetDir);
4752
+ await fs14.ensureDir(PAI_DIR);
4753
+ await fs14.writeFile(CD_FILE, targetDir);
4160
4754
  }
4161
4755
  async function installShellHelper() {
4162
- await fs12.ensureDir(PAI_DIR);
4756
+ await fs14.ensureDir(PAI_DIR);
4163
4757
  if (isWindows) {
4164
4758
  return installPowerShellHelper();
4165
4759
  }
4166
4760
  return installBashHelper();
4167
4761
  }
4168
4762
  async function installBashHelper() {
4169
- await fs12.writeFile(HELPER_FILE_SH, BASH_HELPER);
4763
+ await fs14.writeFile(HELPER_FILE_SH, BASH_HELPER);
4170
4764
  const rcFile = getShellRcPath();
4171
4765
  const sourceLine = 'source "$HOME/.pai/shell-helper.sh"';
4172
- if (await fs12.pathExists(rcFile)) {
4173
- const content = await fs12.readFile(rcFile, "utf8");
4766
+ if (await fs14.pathExists(rcFile)) {
4767
+ const content = await fs14.readFile(rcFile, "utf8");
4174
4768
  if (content.includes("shell-helper.sh")) {
4175
4769
  return true;
4176
4770
  }
4177
- await fs12.appendFile(rcFile, `
4771
+ await fs14.appendFile(rcFile, `
4178
4772
  # PAI \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9
4179
4773
  ${sourceLine}
4180
4774
  `);
4181
4775
  return false;
4182
4776
  }
4183
- await fs12.writeFile(rcFile, `${sourceLine}
4777
+ await fs14.writeFile(rcFile, `${sourceLine}
4184
4778
  `);
4185
4779
  return false;
4186
4780
  }
4187
4781
  async function installPowerShellHelper() {
4188
- await fs12.writeFile(HELPER_FILE_PS1, POWERSHELL_HELPER);
4782
+ await fs14.writeFile(HELPER_FILE_PS1, POWERSHELL_HELPER);
4189
4783
  const rcFile = getShellRcPath();
4190
4784
  const sourceLine = '. "$env:USERPROFILE\\.pai\\shell-helper.ps1"';
4191
- await fs12.ensureDir(join8(rcFile, ".."));
4192
- if (await fs12.pathExists(rcFile)) {
4193
- const content = await fs12.readFile(rcFile, "utf8");
4785
+ await fs14.ensureDir(join8(rcFile, ".."));
4786
+ if (await fs14.pathExists(rcFile)) {
4787
+ const content = await fs14.readFile(rcFile, "utf8");
4194
4788
  if (content.includes("shell-helper.ps1")) {
4195
4789
  return true;
4196
4790
  }
4197
- await fs12.appendFile(rcFile, `
4791
+ await fs14.appendFile(rcFile, `
4198
4792
  # PAI \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9
4199
4793
  ${sourceLine}
4200
4794
  `);
4201
4795
  return false;
4202
4796
  }
4203
- await fs12.writeFile(rcFile, `${sourceLine}
4797
+ await fs14.writeFile(rcFile, `${sourceLine}
4204
4798
  `);
4205
4799
  return false;
4206
4800
  }
@@ -4263,10 +4857,10 @@ __export(claude_settings_exports, {
4263
4857
  mergeOmcIntoSettings: () => mergeOmcIntoSettings
4264
4858
  });
4265
4859
  import os2 from "os";
4266
- import path5 from "path";
4267
- import fs13 from "fs-extra";
4860
+ import path7 from "path";
4861
+ import fs15 from "fs-extra";
4268
4862
  function getClaudeSettingsPath(homeDir = os2.homedir()) {
4269
- return path5.join(homeDir, ".claude", "settings.json");
4863
+ return path7.join(homeDir, ".claude", "settings.json");
4270
4864
  }
4271
4865
  function parseJsonWithBom(raw) {
4272
4866
  const stripped = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
@@ -4281,13 +4875,13 @@ async function enableOmcPlugin(options = {}) {
4281
4875
  const pluginId = options.pluginId ?? DEFAULT_PLUGIN_ID;
4282
4876
  const wantBackup = options.backup ?? true;
4283
4877
  const settingsPath = getClaudeSettingsPath();
4284
- await fs13.ensureDir(path5.dirname(settingsPath));
4285
- if (!await fs13.pathExists(settingsPath)) {
4878
+ await fs15.ensureDir(path7.dirname(settingsPath));
4879
+ if (!await fs15.pathExists(settingsPath)) {
4286
4880
  const skeleton = buildSkeleton(marketplaceId, marketplaceUrl, pluginId);
4287
- await fs13.writeFile(settingsPath, JSON.stringify(skeleton, null, 2) + "\n", "utf8");
4881
+ await fs15.writeFile(settingsPath, JSON.stringify(skeleton, null, 2) + "\n", "utf8");
4288
4882
  return { action: "created", settingsPath };
4289
4883
  }
4290
- const raw = await fs13.readFile(settingsPath, "utf8");
4884
+ const raw = await fs15.readFile(settingsPath, "utf8");
4291
4885
  let parsed;
4292
4886
  try {
4293
4887
  const value = parseJsonWithBom(raw);
@@ -4297,7 +4891,7 @@ async function enableOmcPlugin(options = {}) {
4297
4891
  parsed = value;
4298
4892
  } catch (err) {
4299
4893
  const backupPath2 = `${settingsPath}.backup-${timestampSuffix()}`;
4300
- await fs13.copy(settingsPath, backupPath2);
4894
+ await fs15.copy(settingsPath, backupPath2);
4301
4895
  throw new ClaudeSettingsError(
4302
4896
  `settings.json \uD30C\uC2F1 \uC2E4\uD328: ${err.message}. \uBC31\uC5C5: ${backupPath2}`,
4303
4897
  backupPath2
@@ -4309,10 +4903,10 @@ async function enableOmcPlugin(options = {}) {
4309
4903
  let backupPath;
4310
4904
  if (wantBackup) {
4311
4905
  backupPath = `${settingsPath}.backup-${timestampSuffix()}`;
4312
- await fs13.copy(settingsPath, backupPath);
4906
+ await fs15.copy(settingsPath, backupPath);
4313
4907
  }
4314
4908
  const merged = mergeOmcIntoSettings(parsed, marketplaceId, marketplaceUrl, pluginId);
4315
- await fs13.writeFile(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
4909
+ await fs15.writeFile(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
4316
4910
  return { action: "added", settingsPath, backupPath };
4317
4911
  }
4318
4912
  function buildSkeleton(marketplaceId, marketplaceUrl, pluginId) {
@@ -4454,7 +5048,7 @@ __export(evaluate_cmd_exports, {
4454
5048
  evaluateCommand: () => evaluateCommand
4455
5049
  });
4456
5050
  import { join as join10, basename } from "path";
4457
- import fs14 from "fs-extra";
5051
+ import fs16 from "fs-extra";
4458
5052
  async function evaluateCommand(cwd, options) {
4459
5053
  const useCache = options.cache !== false;
4460
5054
  let llmOutput = useCache ? getCachedResult(cwd) : null;
@@ -4476,15 +5070,15 @@ async function evaluateCommand(cwd, options) {
4476
5070
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4477
5071
  const reportDir = join10(cwd, "docs", "p-reports");
4478
5072
  const reportPath = join10(reportDir, `${today}.md`);
4479
- await fs14.ensureDir(reportDir);
5073
+ await fs16.ensureDir(reportDir);
4480
5074
  const detailedReport = buildDetailedReport(result, projectName);
4481
- await fs14.writeFile(reportPath, detailedReport, "utf8");
5075
+ await fs16.writeFile(reportPath, detailedReport, "utf8");
4482
5076
  console.log("");
4483
5077
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
4484
5078
  console.log("");
4485
5079
  console.log(detailedReport);
4486
5080
  if (options.output) {
4487
- await fs14.writeFile(options.output, detailedReport, "utf8");
5081
+ await fs16.writeFile(options.output, detailedReport, "utf8");
4488
5082
  success(`\uCD94\uAC00 \uC800\uC7A5: ${options.output}`);
4489
5083
  }
4490
5084
  if (options.failUnder && result.totalScore < options.failUnder) {
@@ -4657,7 +5251,7 @@ __export(init_cmd_exports, {
4657
5251
  initCommand: () => initCommand
4658
5252
  });
4659
5253
  import { join as join11, basename as basename2 } from "path";
4660
- import fs15 from "fs-extra";
5254
+ import fs17 from "fs-extra";
4661
5255
  async function initCommand(cwd, nameArg) {
4662
5256
  printWelcomeBanner();
4663
5257
  const { isWindows: isWindows2, diagnoseWindowsEnv: diagnoseWindowsEnv2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
@@ -4695,7 +5289,7 @@ async function initCommand(cwd, nameArg) {
4695
5289
  const isLegacy = await detectLegacyProject(cwd);
4696
5290
  if (isLegacy) {
4697
5291
  const { default: inquirer2 } = await import("inquirer");
4698
- const chalk8 = (await import("chalk")).default;
5292
+ const chalk9 = (await import("chalk")).default;
4699
5293
  console.log("");
4700
5294
  info("\uAE30\uC874 \uD504\uB85C\uC81D\uD2B8\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
4701
5295
  console.log("");
@@ -4716,10 +5310,10 @@ async function initCommand(cwd, nameArg) {
4716
5310
  printReport2(evalResult);
4717
5311
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4718
5312
  const reportDir = join11(cwd, "docs", "p-reports");
4719
- await fs15.ensureDir(reportDir);
5313
+ await fs17.ensureDir(reportDir);
4720
5314
  const legacyName = basename2(cwd);
4721
5315
  const detailedReport = buildDetailedReport3(evalResult, legacyName);
4722
- await fs15.writeFile(join11(reportDir, `${today}.md`), detailedReport, "utf8");
5316
+ await fs17.writeFile(join11(reportDir, `${today}.md`), detailedReport, "utf8");
4723
5317
  console.log("");
4724
5318
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
4725
5319
  } catch {
@@ -4765,7 +5359,7 @@ async function initCommand(cwd, nameArg) {
4765
5359
  projectName = answer.name.trim();
4766
5360
  }
4767
5361
  const projectDir = join11(cwd, projectName);
4768
- if (await fs15.pathExists(projectDir)) {
5362
+ if (await fs17.pathExists(projectDir)) {
4769
5363
  const existingConfig = await loadConfig(projectDir);
4770
5364
  if (existingConfig) {
4771
5365
  console.log("");
@@ -4778,7 +5372,7 @@ async function initCommand(cwd, nameArg) {
4778
5372
  warn(`${projectName}/ \uD3F4\uB354\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4.`);
4779
5373
  hint("\uC774 \uD3F4\uB354\uC5D0 PAI\uB97C \uC124\uCE58\uD569\uB2C8\uB2E4.");
4780
5374
  } else {
4781
- await fs15.ensureDir(projectDir);
5375
+ await fs17.ensureDir(projectDir);
4782
5376
  success(`${projectName}/ \uD3F4\uB354 \uC0DD\uC131`);
4783
5377
  }
4784
5378
  await setupInDirectory(projectDir, projectName);
@@ -4816,7 +5410,7 @@ async function setupInDirectory(projectDir, projectName) {
4816
5410
  }
4817
5411
  async function showCompletion(projectName, projectDir, extraTools, isCurrentDir) {
4818
5412
  const { default: inquirer } = await import("inquirer");
4819
- const chalk8 = (await import("chalk")).default;
5413
+ const chalk9 = (await import("chalk")).default;
4820
5414
  const { sleep: sleep2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
4821
5415
  step(3, 3, "\uC644\uB8CC");
4822
5416
  console.log(colors.success(" \uD504\uB85C\uC81D\uD2B8\uAC00 \uC900\uBE44\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"));
@@ -4830,26 +5424,26 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
4830
5424
  console.log("");
4831
5425
  console.log(colors.accent(" \uC124\uCE58\uB41C \uD50C\uB7EC\uADF8\uC778 & \uAE30\uB2A5"));
4832
5426
  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"));
4833
- console.log(` ${chalk8.cyan("OpenSpec+OMC")} \uC124\uACC4\uB3C4 & \uC9C0\uD615\uB3C4 \u2014 \uBB34\uC5C7\uC744, \uC5B4\uB514\uC5D0 \uB9CC\uB4E4\uC9C0 \uC815\uC758`);
4834
- console.log(` ${chalk8.cyan("RoboCo CLI")} \uD604\uC7A5 \uC18C\uC7A5 \u2014 \uC804\uCCB4 \uACF5\uC815 \uAD00\uB9AC, AI\uC5D0\uAC8C \uC791\uC5C5 \uC9C0\uC2DC`);
4835
- console.log(` ${chalk8.cyan("Vibe-Ready")} \uD488\uC9C8 \uAC80\uC218 \u2014 AI \uAC1C\uBC1C \uC900\uBE44\uB3C4 6\uCE74\uD14C\uACE0\uB9AC \uD3C9\uAC00`);
5427
+ console.log(` ${chalk9.cyan("OpenSpec+OMC")} \uC124\uACC4\uB3C4 & \uC9C0\uD615\uB3C4 \u2014 \uBB34\uC5C7\uC744, \uC5B4\uB514\uC5D0 \uB9CC\uB4E4\uC9C0 \uC815\uC758`);
5428
+ console.log(` ${chalk9.cyan("RoboCo CLI")} \uD604\uC7A5 \uC18C\uC7A5 \u2014 \uC804\uCCB4 \uACF5\uC815 \uAD00\uB9AC, AI\uC5D0\uAC8C \uC791\uC5C5 \uC9C0\uC2DC`);
5429
+ console.log(` ${chalk9.cyan("Vibe-Ready")} \uD488\uC9C8 \uAC80\uC218 \u2014 AI \uAC1C\uBC1C \uC900\uBE44\uB3C4 6\uCE74\uD14C\uACE0\uB9AC \uD3C9\uAC00`);
4836
5430
  if (extraTools.includes("omc")) {
4837
- console.log(` ${chalk8.cyan("OMC")} AI \uC5D0\uC774\uC804\uD2B8 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158 (oh-my-claudecode)`);
5431
+ console.log(` ${chalk9.cyan("OMC")} AI \uC5D0\uC774\uC804\uD2B8 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158 (oh-my-claudecode)`);
4838
5432
  }
4839
5433
  if (extraTools.includes("gstack")) {
4840
- console.log(` ${chalk8.cyan("gstack")} \uD45C\uC900 \uC790\uC7AC & \uACF5\uBC95 \u2014 \uAE30\uC220 \uC2A4\uD0DD \uD45C\uC900 \uAD6C\uC870 \uC81C\uACF5`);
5434
+ console.log(` ${chalk9.cyan("gstack")} \uD45C\uC900 \uC790\uC7AC & \uACF5\uBC95 \u2014 \uAE30\uC220 \uC2A4\uD0DD \uD45C\uC900 \uAD6C\uC870 \uC81C\uACF5`);
4841
5435
  }
4842
5436
  if (extraTools.includes("harness")) {
4843
- console.log(` ${chalk8.cyan("Harness")} \uC548\uC804\uC7A5\uCE58 & \uAC80\uC0AC\uB300 \u2014 AI \uCF54\uB4DC \uC791\uB3D9 \uD14C\uC2A4\uD2B8`);
5437
+ console.log(` ${chalk9.cyan("Harness")} \uC548\uC804\uC7A5\uCE58 & \uAC80\uC0AC\uB300 \u2014 AI \uCF54\uB4DC \uC791\uB3D9 \uD14C\uC2A4\uD2B8`);
4844
5438
  }
4845
5439
  if (extraTools.includes("vercel")) {
4846
- console.log(` ${chalk8.cyan("Vercel")} \uC790\uB3D9 \uBC30\uD3EC \u2014 Preview + Production`);
5440
+ console.log(` ${chalk9.cyan("Vercel")} \uC790\uB3D9 \uBC30\uD3EC \u2014 Preview + Production`);
4847
5441
  }
4848
5442
  if (extraTools.includes("supabase")) {
4849
- console.log(` ${chalk8.cyan("Supabase")} DB + \uC778\uC99D + \uC2A4\uD1A0\uB9AC\uC9C0 (PostgreSQL \uAE30\uBC18)`);
5443
+ console.log(` ${chalk9.cyan("Supabase")} DB + \uC778\uC99D + \uC2A4\uD1A0\uB9AC\uC9C0 (PostgreSQL \uAE30\uBC18)`);
4850
5444
  }
4851
5445
  if (extraTools.includes("mcp")) {
4852
- console.log(` ${chalk8.cyan("MCP \uC11C\uBC84")} AI \uB3C4\uAD6C \u2014 Claude\uAC00 \uD638\uCD9C\uD560 \uCEE4\uC2A4\uD140 \uAE30\uB2A5/\uB370\uC774\uD130`);
5446
+ console.log(` ${chalk9.cyan("MCP \uC11C\uBC84")} AI \uB3C4\uAD6C \u2014 Claude\uAC00 \uD638\uCD9C\uD560 \uCEE4\uC2A4\uD140 \uAE30\uB2A5/\uB370\uC774\uD130`);
4853
5447
  }
4854
5448
  console.log("");
4855
5449
  console.log(colors.accent(" \uC124\uCE58 \uD655\uC778"));
@@ -4863,7 +5457,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
4863
5457
  ...extraTools.includes("omc") ? [{ label: "OMC \uB7F0\uD0C0\uC784", path: ".omc" }] : []
4864
5458
  ];
4865
5459
  for (const check of checks) {
4866
- const exists = await fs15.pathExists(join11(projectDir, check.path));
5460
+ const exists = await fs17.pathExists(join11(projectDir, check.path));
4867
5461
  console.log(` ${exists ? colors.success("\u2713") : colors.err("\u2717")} ${check.label.padEnd(16)} ${colors.dim(check.path)}`);
4868
5462
  }
4869
5463
  console.log("");
@@ -4889,9 +5483,9 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
4889
5483
  await sleep2(500);
4890
5484
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4891
5485
  const reportDir = join11(projectDir, "docs", "p-reports");
4892
- await fs15.ensureDir(reportDir);
5486
+ await fs17.ensureDir(reportDir);
4893
5487
  const detailedReport = buildDetailedReport3(evalResult, projectName);
4894
- await fs15.writeFile(join11(reportDir, `${today}.md`), detailedReport, "utf8");
5488
+ await fs17.writeFile(join11(reportDir, `${today}.md`), detailedReport, "utf8");
4895
5489
  await sleep2(500);
4896
5490
  console.log("");
4897
5491
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
@@ -4909,7 +5503,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
4909
5503
  }
4910
5504
  console.log("");
4911
5505
  if (!isCurrentDir) {
4912
- console.log(` ${chalk8.green("\u2192")} cd ${projectName} ${colors.dim("\uC774\uB3D9 \uC644\uB8CC")}`);
5506
+ console.log(` ${chalk9.green("\u2192")} cd ${projectName} ${colors.dim("\uC774\uB3D9 \uC644\uB8CC")}`);
4913
5507
  }
4914
5508
  console.log("");
4915
5509
  success("\uC774\uC81C Claude Code\uC640 \uD568\uAED8 PRD \uBB38\uC11C\uB97C \uC791\uC131\uD558\uC138\uC694.");
@@ -4942,7 +5536,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
4942
5536
  const shellRc = getShellRcPath2();
4943
5537
  let yoloAlreadyAliased = false;
4944
5538
  try {
4945
- const rcContent = await fs15.readFile(shellRc, "utf8");
5539
+ const rcContent = await fs17.readFile(shellRc, "utf8");
4946
5540
  yoloAlreadyAliased = checkYolo(rcContent);
4947
5541
  } catch {
4948
5542
  }
@@ -4962,10 +5556,10 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
4962
5556
  try {
4963
5557
  const { getYoloAliasLine: getYoloAliasLine2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
4964
5558
  const aliasLine = getYoloAliasLine2();
4965
- const rcContent = await fs15.readFile(shellRc, "utf8").catch(() => "");
5559
+ const rcContent = await fs17.readFile(shellRc, "utf8").catch(() => "");
4966
5560
  if (!rcContent.includes("claude-yolo")) {
4967
- await fs15.ensureDir(join11(shellRc, ".."));
4968
- await fs15.appendFile(shellRc, `
5561
+ await fs17.ensureDir(join11(shellRc, ".."));
5562
+ await fs17.appendFile(shellRc, `
4969
5563
  # PAI \u2014 claude-YOLO mode
4970
5564
  ${aliasLine}
4971
5565
  `);
@@ -5004,7 +5598,7 @@ ${aliasLine}
5004
5598
  async function handleExistingProject(cwd, state) {
5005
5599
  const { PLUGIN_META: PLUGIN_META2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
5006
5600
  const { default: inquirer } = await import("inquirer");
5007
- const chalk8 = (await import("chalk")).default;
5601
+ const chalk9 = (await import("chalk")).default;
5008
5602
  section("\uAE30\uC874 \uD504\uB85C\uC81D\uD2B8");
5009
5603
  const allPlugins = Object.entries(PLUGIN_META2).map(([key, meta]) => ({
5010
5604
  key,
@@ -5015,7 +5609,7 @@ async function handleExistingProject(cwd, state) {
5015
5609
  if (p.installed) {
5016
5610
  success(p.label);
5017
5611
  } else {
5018
- console.log(colors.dim(` \xB7 ${p.label} ${chalk8.gray("\uBBF8\uC124\uCE58")}`));
5612
+ console.log(colors.dim(` \xB7 ${p.label} ${chalk9.gray("\uBBF8\uC124\uCE58")}`));
5019
5613
  }
5020
5614
  }
5021
5615
  const currentConfig = await loadConfig(cwd);
@@ -5041,7 +5635,7 @@ async function handleExistingProject(cwd, state) {
5041
5635
  name: `\u{1FA7A} \uD658\uACBD \uC810\uAC80 ${colors.dim("(pai check \u2014 Node/Git/Claude/OMC)")}`,
5042
5636
  value: "doctor"
5043
5637
  },
5044
- { name: chalk8.gray("\u{1F6AA} \uC885\uB8CC"), value: "exit" }
5638
+ { name: chalk9.gray("\u{1F6AA} \uC885\uB8CC"), value: "exit" }
5045
5639
  ]
5046
5640
  }]);
5047
5641
  switch (action) {
@@ -5066,7 +5660,7 @@ async function handleExistingProject(cwd, state) {
5066
5660
  }
5067
5661
  async function installOrchestratorOnly(projectDir, projectName) {
5068
5662
  const { default: inquirer } = await import("inquirer");
5069
- const chalk8 = (await import("chalk")).default;
5663
+ const chalk9 = (await import("chalk")).default;
5070
5664
  const { generateFiles: generateFiles2 } = await Promise.resolve().then(() => (init_generator(), generator_exports));
5071
5665
  const { analyzeProject: analyzeProject2 } = await Promise.resolve().then(() => (init_analyzer(), analyzer_exports));
5072
5666
  const { provisionClaudeCommands: provisionClaudeCommands2 } = await Promise.resolve().then(() => (init_claude_commands(), claude_commands_exports));
@@ -5088,7 +5682,7 @@ async function installOrchestratorOnly(projectDir, projectName) {
5088
5682
  { name: `gstack ${colors.dim("\u2500 \uD45C\uC900 \uC790\uC7AC & \uACF5\uBC95 (\uAE30\uC220 \uC2A4\uD0DD)")}`, value: "gstack" },
5089
5683
  { name: `Harness ${colors.dim("\u2500 \uC548\uC804\uC7A5\uCE58 & \uAC80\uC0AC\uB300 (\uD14C\uC2A4\uD2B8)")}`, value: "harness" },
5090
5684
  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"),
5091
- { name: chalk8.green("\u21B5 \uC120\uD0DD \uC644\uB8CC"), value: "__done__" }
5685
+ { name: chalk9.green("\u21B5 \uC120\uD0DD \uC644\uB8CC"), value: "__done__" }
5092
5686
  ]
5093
5687
  }]);
5094
5688
  const selectedPlugins = plugins.filter((p) => p !== "__done__");
@@ -5159,8 +5753,8 @@ async function installOrchestratorOnly(projectDir, projectName) {
5159
5753
  printReport2(evalResult);
5160
5754
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5161
5755
  const reportDir = join11(projectDir, "docs", "p-reports");
5162
- await fs15.ensureDir(reportDir);
5163
- await fs15.writeFile(join11(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
5756
+ await fs17.ensureDir(reportDir);
5757
+ await fs17.writeFile(join11(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
5164
5758
  console.log("");
5165
5759
  hint(`\uC0C1\uC138 \uB9AC\uD3EC\uD2B8: docs/p-reports/${today}.md`);
5166
5760
  } catch {
@@ -5175,7 +5769,7 @@ async function installOrchestratorOnly(projectDir, projectName) {
5175
5769
  message: "Claude Code\uB97C \uC2DC\uC791\uD560\uAE4C\uC694?",
5176
5770
  choices: [
5177
5771
  { name: `Claude \uBC14\uB85C \uC2DC\uC791 ${colors.dim("\u2500 claude")}`, value: "claude" },
5178
- { name: chalk8.gray("\uB098\uC911\uC5D0 \uC9C1\uC811 \uC2E4\uD589"), value: "none" }
5772
+ { name: chalk9.gray("\uB098\uC911\uC5D0 \uC9C1\uC811 \uC2E4\uD589"), value: "none" }
5179
5773
  ]
5180
5774
  }]);
5181
5775
  if (launch === "none") {
@@ -5210,7 +5804,7 @@ async function detectLegacyProject(cwd) {
5210
5804
  ".gitignore"
5211
5805
  ];
5212
5806
  for (const signal of signals) {
5213
- if (await fs15.pathExists(join11(cwd, signal))) return true;
5807
+ if (await fs17.pathExists(join11(cwd, signal))) return true;
5214
5808
  }
5215
5809
  return false;
5216
5810
  }
@@ -5302,16 +5896,16 @@ var init_help_cmd = __esm({
5302
5896
 
5303
5897
  // src/stages/design/openspec.ts
5304
5898
  import { join as join12 } from "path";
5305
- import fs16 from "fs-extra";
5899
+ import fs18 from "fs-extra";
5306
5900
  async function initOpenSpec(cwd, projectName) {
5307
5901
  const docsDir = join12(cwd, "docs");
5308
- await fs16.ensureDir(docsDir);
5902
+ await fs18.ensureDir(docsDir);
5309
5903
  const openspecPath = join12(docsDir, "openspec.md");
5310
- if (await fs16.pathExists(openspecPath)) {
5904
+ if (await fs18.pathExists(openspecPath)) {
5311
5905
  info("docs/openspec.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
5312
5906
  return;
5313
5907
  }
5314
- await fs16.writeFile(openspecPath, [
5908
+ await fs18.writeFile(openspecPath, [
5315
5909
  `# OpenSpec \u2014 ${projectName}`,
5316
5910
  "",
5317
5911
  "## 1. \uBAA9\uC801 (Purpose)",
@@ -5345,7 +5939,7 @@ async function validateOpenSpec(cwd) {
5345
5939
  ];
5346
5940
  let specPath = null;
5347
5941
  for (const p of candidates) {
5348
- if (await fs16.pathExists(p)) {
5942
+ if (await fs18.pathExists(p)) {
5349
5943
  specPath = p;
5350
5944
  break;
5351
5945
  }
@@ -5359,7 +5953,7 @@ async function validateOpenSpec(cwd) {
5359
5953
  warnings: ["openspec.md \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `pai design init` \uC744 \uC2E4\uD589\uD558\uC138\uC694."]
5360
5954
  };
5361
5955
  }
5362
- const content = await fs16.readFile(specPath, "utf8");
5956
+ const content = await fs18.readFile(specPath, "utf8");
5363
5957
  const missing = [];
5364
5958
  let filled = 0;
5365
5959
  for (const section2 of REQUIRED_SECTIONS) {
@@ -5408,16 +6002,16 @@ var init_openspec = __esm({
5408
6002
 
5409
6003
  // src/stages/design/omc.ts
5410
6004
  import { join as join13 } from "path";
5411
- import fs17 from "fs-extra";
6005
+ import fs19 from "fs-extra";
5412
6006
  async function initOMC(cwd, projectName) {
5413
6007
  const paiDir = join13(cwd, ".pai");
5414
- await fs17.ensureDir(paiDir);
6008
+ await fs19.ensureDir(paiDir);
5415
6009
  const omcPath = join13(paiDir, "omc.md");
5416
- if (await fs17.pathExists(omcPath)) {
6010
+ if (await fs19.pathExists(omcPath)) {
5417
6011
  info(".pai/omc.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
5418
6012
  return;
5419
6013
  }
5420
- await fs17.writeFile(omcPath, [
6014
+ await fs19.writeFile(omcPath, [
5421
6015
  `# OMC \u2014 Object Model Context (${projectName})`,
5422
6016
  "",
5423
6017
  "> AI\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uB3C4\uBA54\uC778\uC744 \uC774\uD574\uD558\uAE30 \uC704\uD55C \uD575\uC2EC \uAC1D\uCCB4 \uBAA8\uB378",
@@ -5500,14 +6094,14 @@ var init_design_cmd = __esm({
5500
6094
 
5501
6095
  // src/stages/validation/runner.ts
5502
6096
  import { join as join14 } from "path";
5503
- import fs18 from "fs-extra";
6097
+ import fs20 from "fs-extra";
5504
6098
  async function runTests(cwd) {
5505
6099
  const start = Date.now();
5506
6100
  const gstackPath = join14(cwd, ".pai", "gstack.json");
5507
6101
  let runner = "npm test";
5508
- if (await fs18.pathExists(gstackPath)) {
6102
+ if (await fs20.pathExists(gstackPath)) {
5509
6103
  try {
5510
- const config = await fs18.readJson(gstackPath);
6104
+ const config = await fs20.readJson(gstackPath);
5511
6105
  if (config.testRunner === "vitest") runner = "npx vitest run";
5512
6106
  else if (config.testRunner === "jest") runner = "npx jest";
5513
6107
  else if (config.testRunner === "mocha") runner = "npx mocha";
@@ -5515,9 +6109,9 @@ async function runTests(cwd) {
5515
6109
  }
5516
6110
  }
5517
6111
  const pkgPath = join14(cwd, "package.json");
5518
- if (await fs18.pathExists(pkgPath)) {
6112
+ if (await fs20.pathExists(pkgPath)) {
5519
6113
  try {
5520
- const pkg5 = await fs18.readJson(pkgPath);
6114
+ const pkg5 = await fs20.readJson(pkgPath);
5521
6115
  if (!pkg5.scripts?.test || pkg5.scripts.test.includes("no test specified")) {
5522
6116
  return {
5523
6117
  runner,
@@ -5560,15 +6154,15 @@ var init_runner = __esm({
5560
6154
 
5561
6155
  // src/stages/validation/harness.ts
5562
6156
  import { join as join15 } from "path";
5563
- import fs19 from "fs-extra";
6157
+ import fs21 from "fs-extra";
5564
6158
  async function runHarnessCheck(cwd) {
5565
6159
  const harnessPath = join15(cwd, ".pai", "harness.json");
5566
- if (!await fs19.pathExists(harnessPath)) {
6160
+ if (!await fs21.pathExists(harnessPath)) {
5567
6161
  return { enabled: false, specFile: null, rules: [], checks: [] };
5568
6162
  }
5569
6163
  let config;
5570
6164
  try {
5571
- config = await fs19.readJson(harnessPath);
6165
+ config = await fs21.readJson(harnessPath);
5572
6166
  } catch {
5573
6167
  return { enabled: false, specFile: null, rules: [], checks: [] };
5574
6168
  }
@@ -5576,8 +6170,8 @@ async function runHarnessCheck(cwd) {
5576
6170
  const rules = config.rules ?? [];
5577
6171
  const checks = [];
5578
6172
  if (rules.includes("spec-implementation-match")) {
5579
- const specExists = await fs19.pathExists(join15(cwd, specFile));
5580
- const srcExists = await fs19.pathExists(join15(cwd, "src"));
6173
+ const specExists = await fs21.pathExists(join15(cwd, specFile));
6174
+ const srcExists = await fs21.pathExists(join15(cwd, "src"));
5581
6175
  checks.push({
5582
6176
  rule: "spec-implementation-match",
5583
6177
  passed: specExists && srcExists,
@@ -5585,8 +6179,8 @@ async function runHarnessCheck(cwd) {
5585
6179
  });
5586
6180
  }
5587
6181
  if (rules.includes("api-contract-test")) {
5588
- const testDir = await fs19.pathExists(join15(cwd, "tests"));
5589
- const testDir2 = await fs19.pathExists(join15(cwd, "test"));
6182
+ const testDir = await fs21.pathExists(join15(cwd, "tests"));
6183
+ const testDir2 = await fs21.pathExists(join15(cwd, "test"));
5590
6184
  checks.push({
5591
6185
  rule: "api-contract-test",
5592
6186
  passed: testDir || testDir2,
@@ -5701,13 +6295,13 @@ var init_context = __esm({
5701
6295
 
5702
6296
  // src/stages/design/index.ts
5703
6297
  import { join as join16 } from "path";
5704
- import fs20 from "fs-extra";
6298
+ import fs22 from "fs-extra";
5705
6299
  async function autoInstallHarness(cwd) {
5706
6300
  const harnessPath = join16(cwd, ".pai", "harness.json");
5707
- if (await fs20.pathExists(harnessPath)) return;
6301
+ if (await fs22.pathExists(harnessPath)) return;
5708
6302
  await withSpinner("Harness Engineering \uC790\uB3D9 \uC124\uC815 \uC911...", async () => {
5709
- await fs20.ensureDir(join16(cwd, ".pai"));
5710
- await fs20.writeJson(harnessPath, {
6303
+ await fs22.ensureDir(join16(cwd, ".pai"));
6304
+ await fs22.writeJson(harnessPath, {
5711
6305
  version: "1.0",
5712
6306
  specFile: "docs/openspec.md",
5713
6307
  checkOn: ["pre-commit", "ci"],
@@ -6064,7 +6658,7 @@ __export(remove_cmd_exports, {
6064
6658
  removeCommand: () => removeCommand
6065
6659
  });
6066
6660
  import { basename as basename4, dirname } from "path";
6067
- import fs21 from "fs-extra";
6661
+ import fs23 from "fs-extra";
6068
6662
  async function removeCommand(cwd, options) {
6069
6663
  section("\uD504\uB85C\uC81D\uD2B8 \uC0AD\uC81C");
6070
6664
  const config = await loadConfig(cwd);
@@ -6082,7 +6676,7 @@ async function removeCommand(cwd, options) {
6082
6676
  console.log(colors.err(` ${folderName}/ \uD3F4\uB354 \uC804\uCCB4\uAC00 \uC0AD\uC81C\uB429\uB2C8\uB2E4.`));
6083
6677
  hint("\uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
6084
6678
  console.log("");
6085
- const items = await fs21.readdir(cwd);
6679
+ const items = await fs23.readdir(cwd);
6086
6680
  const fileCount = items.filter((i) => !i.startsWith(".")).length;
6087
6681
  const hiddenCount = items.filter((i) => i.startsWith(".")).length;
6088
6682
  info(`\uD30C\uC77C/\uD3F4\uB354 ${fileCount}\uAC1C, \uC228\uAE40 \uD56D\uBAA9 ${hiddenCount}\uAC1C`);
@@ -6101,7 +6695,7 @@ async function removeCommand(cwd, options) {
6101
6695
  }
6102
6696
  process.chdir(parentDir);
6103
6697
  try {
6104
- await fs21.remove(cwd);
6698
+ await fs23.remove(cwd);
6105
6699
  console.log("");
6106
6700
  success(`${folderName}/ \uD504\uB85C\uC81D\uD2B8\uAC00 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
6107
6701
  try {
@@ -6222,7 +6816,7 @@ __export(upgrade_cmd_exports, {
6222
6816
  });
6223
6817
  import { createRequire as createRequire3 } from "module";
6224
6818
  import { fileURLToPath } from "url";
6225
- import chalk5 from "chalk";
6819
+ import chalk6 from "chalk";
6226
6820
  async function upgradeCommand(cwd, options) {
6227
6821
  section("PAI \uC5C5\uADF8\uB808\uC774\uB4DC");
6228
6822
  const currentVersion = pkg3.version;
@@ -6245,8 +6839,8 @@ async function upgradeCommand(cwd, options) {
6245
6839
  }
6246
6840
  const configVersion = config.version;
6247
6841
  console.log("");
6248
- console.log(` \uD504\uB85C\uC81D\uD2B8 config: ${chalk5.gray(`v${configVersion}`)}`);
6249
- console.log(` \uD604\uC7AC CLI: ${chalk5.cyan(`v${currentVersion}`)}`);
6842
+ console.log(` \uD504\uB85C\uC81D\uD2B8 config: ${chalk6.gray(`v${configVersion}`)}`);
6843
+ console.log(` \uD604\uC7AC CLI: ${chalk6.cyan(`v${currentVersion}`)}`);
6250
6844
  if (!options.force && compareSemver(configVersion, currentVersion) >= 0) {
6251
6845
  console.log("");
6252
6846
  success("\uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C\uC774 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4.");
@@ -6264,7 +6858,7 @@ async function upgradeCommand(cwd, options) {
6264
6858
  if (result.commandsWritten.length > 0) {
6265
6859
  success(`\uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC ${result.commandsWritten.length}\uAC1C \uC5C5\uB370\uC774\uD2B8`);
6266
6860
  for (const f of result.commandsWritten) {
6267
- console.log(chalk5.gray(` ${f}`));
6861
+ console.log(chalk6.gray(` ${f}`));
6268
6862
  }
6269
6863
  }
6270
6864
  if (result.commandErrors.length > 0) {
@@ -6284,7 +6878,7 @@ async function upgradeCli(currentVersion, pkgName, force) {
6284
6878
  const scriptPath = fileURLToPath(import.meta.url);
6285
6879
  const mode = detectInstallMode(scriptPath);
6286
6880
  console.log("");
6287
- console.log(` \uD604\uC7AC CLI: ${chalk5.cyan(`v${currentVersion}`)} ${chalk5.gray(`(${mode})`)}`);
6881
+ console.log(` \uD604\uC7AC CLI: ${chalk6.cyan(`v${currentVersion}`)} ${chalk6.gray(`(${mode})`)}`);
6288
6882
  if (mode === "npx") {
6289
6883
  info("npx\uB294 \uB9E4\uBC88 \uCD5C\uC2E0 \uBC84\uC804\uC744 \uAC00\uC838\uC624\uBBC0\uB85C \uBCC4\uB3C4 \uC5C5\uADF8\uB808\uC774\uB4DC\uAC00 \uBD88\uD544\uC694\uD569\uB2C8\uB2E4.");
6290
6884
  hint("\uB2E4\uC74C npx \uC2E4\uD589 \uC2DC \uC790\uB3D9\uC73C\uB85C \uCD5C\uC2E0 \uBC84\uC804\uC744 \uC0AC\uC6A9\uD569\uB2C8\uB2E4.");
@@ -6299,7 +6893,7 @@ async function upgradeCli(currentVersion, pkgName, force) {
6299
6893
  hint(`\uC218\uB3D9 \uC5C5\uADF8\uB808\uC774\uB4DC: npm install -g ${pkgName}@latest`);
6300
6894
  return;
6301
6895
  }
6302
- console.log(` \uCD5C\uC2E0: ${chalk5.cyan(`v${latestVersion}`)}`);
6896
+ console.log(` \uCD5C\uC2E0: ${chalk6.cyan(`v${latestVersion}`)}`);
6303
6897
  const cmp = compareSemver(currentVersion, latestVersion);
6304
6898
  if (!force && cmp >= 0) {
6305
6899
  console.log("");
@@ -6380,8 +6974,8 @@ __export(savetoken_cmd_exports, {
6380
6974
  savetokenCommand: () => savetokenCommand
6381
6975
  });
6382
6976
  import { join as join17, relative } from "path";
6383
- import fs22 from "fs-extra";
6384
- import chalk6 from "chalk";
6977
+ import fs24 from "fs-extra";
6978
+ import chalk7 from "chalk";
6385
6979
  async function savetokenCommand(cwd) {
6386
6980
  const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
6387
6981
  console.log("");
@@ -6405,14 +6999,14 @@ async function savetokenCommand(cwd) {
6405
6999
  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"));
6406
7000
  for (const sdk of sdks) {
6407
7001
  const count = callSites.filter((s) => s.sdk === sdk).length;
6408
- console.log(` ${chalk6.cyan(sdk.padEnd(20))} ${chalk6.white(String(count))}\uAC74`);
7002
+ console.log(` ${chalk7.cyan(sdk.padEnd(20))} ${chalk7.white(String(count))}\uAC74`);
6409
7003
  }
6410
7004
  if (imports.length > 0) {
6411
7005
  console.log("");
6412
7006
  console.log(colors.dim(" SDK \uC784\uD3EC\uD2B8"));
6413
7007
  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"));
6414
7008
  for (const site of imports) {
6415
- console.log(` ${colors.dim(site.file)}:${chalk6.yellow(String(site.line))}`);
7009
+ console.log(` ${colors.dim(site.file)}:${chalk7.yellow(String(site.line))}`);
6416
7010
  console.log(` ${site.content.trim().substring(0, 80)}`);
6417
7011
  }
6418
7012
  }
@@ -6421,7 +7015,7 @@ async function savetokenCommand(cwd) {
6421
7015
  console.log(colors.dim(" AI API \uD638\uCD9C \uC9C0\uC810"));
6422
7016
  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"));
6423
7017
  for (const site of apiCalls) {
6424
- console.log(` ${colors.dim(site.file)}:${chalk6.yellow(String(site.line))} ${chalk6.cyan(site.sdk)}`);
7018
+ console.log(` ${colors.dim(site.file)}:${chalk7.yellow(String(site.line))} ${chalk7.cyan(site.sdk)}`);
6425
7019
  console.log(` ${site.content.trim().substring(0, 80)}`);
6426
7020
  }
6427
7021
  }
@@ -6430,7 +7024,7 @@ async function savetokenCommand(cwd) {
6430
7024
  console.log(colors.dim(" Fetch \uAE30\uBC18 AI \uD638\uCD9C"));
6431
7025
  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"));
6432
7026
  for (const site of fetchCalls) {
6433
- console.log(` ${colors.dim(site.file)}:${chalk6.yellow(String(site.line))} ${chalk6.cyan(site.sdk)}`);
7027
+ console.log(` ${colors.dim(site.file)}:${chalk7.yellow(String(site.line))} ${chalk7.cyan(site.sdk)}`);
6434
7028
  console.log(` ${site.content.trim().substring(0, 80)}`);
6435
7029
  }
6436
7030
  }
@@ -6438,22 +7032,22 @@ async function savetokenCommand(cwd) {
6438
7032
  console.log("");
6439
7033
  console.log(colors.accent(" \uC808\uAC10 \uAC00\uB2A5\uC131 \uCD94\uC815"));
6440
7034
  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"));
6441
- console.log(` \uCD1D API \uD638\uCD9C \uC9C0\uC810 ${chalk6.white(String(totalApiPoints))}\uAC74`);
6442
- console.log(` \uBD84\uC11D \uD544\uC694 ${chalk6.yellow("Claude Code\uC5D0\uC11C \uC2EC\uCE35 \uBD84\uC11D \uD544\uC694")}`);
7035
+ console.log(` \uCD1D API \uD638\uCD9C \uC9C0\uC810 ${chalk7.white(String(totalApiPoints))}\uAC74`);
7036
+ console.log(` \uBD84\uC11D \uD544\uC694 ${chalk7.yellow("Claude Code\uC5D0\uC11C \uC2EC\uCE35 \uBD84\uC11D \uD544\uC694")}`);
6443
7037
  console.log("");
6444
7038
  console.log(colors.dim(" \uC601\uD5A5\uB3C4\uBCC4 \uB300\uCCB4 \uC804\uB7B5"));
6445
7039
  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"));
6446
- console.log(` ${chalk6.green("\u25CF")} \uB0AE\uC74C \uD14D\uC2A4\uD2B8 \uD3EC\uB9F7\uD305, \uC720\uD6A8\uC131 \uAC80\uC99D, \uD15C\uD50C\uB9BF \uC0DD\uC131`);
7040
+ console.log(` ${chalk7.green("\u25CF")} \uB0AE\uC74C \uD14D\uC2A4\uD2B8 \uD3EC\uB9F7\uD305, \uC720\uD6A8\uC131 \uAC80\uC99D, \uD15C\uD50C\uB9BF \uC0DD\uC131`);
6447
7041
  console.log(` \u2192 ${colors.dim("regex, JSON schema, \uD15C\uD50C\uB9BF \uC5D4\uC9C4\uC73C\uB85C \uC989\uC2DC \uB300\uCCB4")}`);
6448
- console.log(` ${chalk6.yellow("\u25CF")} \uC911\uAC04 \uB370\uC774\uD130 \uBD84\uB958, \uC694\uC57D, \uAC04\uB2E8\uD55C \uCD94\uCD9C`);
7042
+ console.log(` ${chalk7.yellow("\u25CF")} \uC911\uAC04 \uB370\uC774\uD130 \uBD84\uB958, \uC694\uC57D, \uAC04\uB2E8\uD55C \uCD94\uCD9C`);
6449
7043
  console.log(` \u2192 ${colors.dim("\uB8F0 \uAE30\uBC18 \uB85C\uC9C1, \uD0A4\uC6CC\uB4DC \uB9E4\uCE6D\uC73C\uB85C \uAC80\uD1A0 \uD6C4 \uB300\uCCB4")}`);
6450
- console.log(` ${chalk6.red("\u25CF")} \uB192\uC74C \uCF54\uB4DC \uC0DD\uC131, \uBCF5\uC7A1\uD55C \uCD94\uB860, \uCC3D\uC758\uC801 \uC0DD\uC131`);
7044
+ console.log(` ${chalk7.red("\u25CF")} \uB192\uC74C \uCF54\uB4DC \uC0DD\uC131, \uBCF5\uC7A1\uD55C \uCD94\uB860, \uCC3D\uC758\uC801 \uC0DD\uC131`);
6451
7045
  console.log(` \u2192 ${colors.dim("AI \uD544\uC218 \u2014 \uD504\uB86C\uD504\uD2B8 \uCD5C\uC801\uD654\uB85C \uD1A0\uD070 \uC808\uAC10")}`);
6452
7046
  const reportDir = join17(cwd, ".pai");
6453
- await fs22.ensureDir(reportDir);
7047
+ await fs24.ensureDir(reportDir);
6454
7048
  const report = buildReport(callSites, cwd);
6455
7049
  const reportPath = join17(reportDir, "savetoken-report.md");
6456
- await fs22.writeFile(reportPath, report, "utf8");
7050
+ await fs24.writeFile(reportPath, report, "utf8");
6457
7051
  console.log("");
6458
7052
  success("\uC2A4\uCE94 \uB9AC\uD3EC\uD2B8 \uC800\uC7A5: .pai/savetoken-report.md");
6459
7053
  console.log("");
@@ -6611,8 +7205,8 @@ __export(wakeup_cmd_exports, {
6611
7205
  });
6612
7206
  import { join as join18 } from "path";
6613
7207
  import { homedir as homedir3, platform as osPlatform } from "os";
6614
- import fs23 from "fs-extra";
6615
- import chalk7 from "chalk";
7208
+ import fs25 from "fs-extra";
7209
+ import chalk8 from "chalk";
6616
7210
  async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
6617
7211
  if (timeOrAction === "off") {
6618
7212
  await disableWakeup();
@@ -6648,8 +7242,8 @@ async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
6648
7242
  name: "launchMode",
6649
7243
  message: "\uC2E4\uD589 \uBAA8\uB4DC:",
6650
7244
  choices: [
6651
- { name: `claude ${chalk7.gray("\uC77C\uBC18 \uBAA8\uB4DC")}`, value: "normal" },
6652
- { name: `claude --dangerously-skip-permissions ${chalk7.gray("claude-YOLO mode")}`, value: "yolo" }
7245
+ { name: `claude ${chalk8.gray("\uC77C\uBC18 \uBAA8\uB4DC")}`, value: "normal" },
7246
+ { name: `claude --dangerously-skip-permissions ${chalk8.gray("claude-YOLO mode")}`, value: "yolo" }
6653
7247
  ]
6654
7248
  }]);
6655
7249
  const config = {
@@ -6658,9 +7252,9 @@ async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
6658
7252
  projectDir,
6659
7253
  launchMode
6660
7254
  };
6661
- await fs23.ensureDir(PAI_DIR2);
6662
- await fs23.writeJson(CONFIG_FILE2, config, { spaces: 2 });
6663
- await fs23.writeJson(MESSAGES_FILE, MESSAGES);
7255
+ await fs25.ensureDir(PAI_DIR2);
7256
+ await fs25.writeJson(CONFIG_FILE2, config, { spaces: 2 });
7257
+ await fs25.writeJson(MESSAGES_FILE, MESSAGES);
6664
7258
  await createWakeupScript(config);
6665
7259
  if (osPlatform() === "darwin") {
6666
7260
  await setupMacOS(config);
@@ -6673,12 +7267,12 @@ async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
6673
7267
  success("\u2600\uFE0F \uC6E8\uC774\uD06C\uC5C5 \uC124\uC815 \uC644\uB8CC");
6674
7268
  console.log("");
6675
7269
  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"));
6676
- console.log(` \uC2DC\uAC04 ${chalk7.white(config.time)}`);
6677
- console.log(` \uC2A4\uCF00\uC904 ${chalk7.white(scheduleToLabel(config.schedule))}`);
6678
- console.log(` \uD504\uB85C\uC81D\uD2B8 ${chalk7.white(config.projectDir)}`);
6679
- console.log(` \uBAA8\uB4DC ${chalk7.white(config.launchMode === "yolo" ? "claude-YOLO mode" : "\uC77C\uBC18 \uBAA8\uB4DC")}`);
7270
+ console.log(` \uC2DC\uAC04 ${chalk8.white(config.time)}`);
7271
+ console.log(` \uC2A4\uCF00\uC904 ${chalk8.white(scheduleToLabel(config.schedule))}`);
7272
+ console.log(` \uD504\uB85C\uC81D\uD2B8 ${chalk8.white(config.projectDir)}`);
7273
+ console.log(` \uBAA8\uB4DC ${chalk8.white(config.launchMode === "yolo" ? "claude-YOLO mode" : "\uC77C\uBC18 \uBAA8\uB4DC")}`);
6680
7274
  if (osPlatform() === "darwin") {
6681
- console.log(` \uC2A4\uCF00\uC904\uB7EC ${chalk7.white("launchd + pmset (wake-from-sleep)")}`);
7275
+ console.log(` \uC2A4\uCF00\uC904\uB7EC ${chalk8.white("launchd + pmset (wake-from-sleep)")}`);
6682
7276
  }
6683
7277
  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"));
6684
7278
  console.log("");
@@ -6691,7 +7285,7 @@ async function setupMacOS(config) {
6691
7285
  const { execa } = await import("execa");
6692
7286
  const [hour, minute] = config.time.split(":").map(Number);
6693
7287
  const plistDir = join18(homedir3(), "Library", "LaunchAgents");
6694
- await fs23.ensureDir(plistDir);
7288
+ await fs25.ensureDir(plistDir);
6695
7289
  const weekdays = scheduleToWeekdays(config.schedule);
6696
7290
  let calendarEntries;
6697
7291
  if (weekdays.length === 7) {
@@ -6727,7 +7321,7 @@ ${calendarEntries}
6727
7321
  <string>${PAI_DIR2}/wakeup.log</string>
6728
7322
  </dict>
6729
7323
  </plist>`;
6730
- await fs23.writeFile(PLIST_PATH, plist);
7324
+ await fs25.writeFile(PLIST_PATH, plist);
6731
7325
  await execa("launchctl", ["unload", PLIST_PATH]).catch(() => {
6732
7326
  });
6733
7327
  await execa("launchctl", ["load", PLIST_PATH]);
@@ -6750,7 +7344,7 @@ async function setupWindows(config) {
6750
7344
  const { execa } = await import("execa");
6751
7345
  const [hour, minute] = config.time.split(":").map(Number);
6752
7346
  const psScriptDir = join18(homedir3(), ".pai");
6753
- await fs23.ensureDir(psScriptDir);
7347
+ await fs25.ensureDir(psScriptDir);
6754
7348
  const psScriptPath = join18(psScriptDir, "wakeup.ps1");
6755
7349
  const claudeCmd = config.launchMode === "yolo" ? "claude --dangerously-skip-permissions" : "claude";
6756
7350
  const psScript = `# PAI Wakeup \u2014 Claude Code \uC138\uC158 \uC790\uB3D9 \uC2DC\uC791
@@ -6780,7 +7374,7 @@ $notifier.Show([Windows.UI.Notifications.ToastNotification]::new($xml))
6780
7374
  # Open PowerShell with Claude Code
6781
7375
  Start-Process powershell -ArgumentList "-NoExit", "-Command", "Get-Content '$todayFile'; Write-Host ''; Set-Location '${config.projectDir}'; ${claudeCmd}"
6782
7376
  `;
6783
- await fs23.writeFile(psScriptPath, psScript, "utf8");
7377
+ await fs25.writeFile(psScriptPath, psScript, "utf8");
6784
7378
  const daysMap = {
6785
7379
  "\uD3C9\uC77C": "MON,TUE,WED,THU,FRI",
6786
7380
  "\uB9E4\uC77C": "MON,TUE,WED,THU,FRI,SAT,SUN",
@@ -6828,7 +7422,7 @@ async function disableWakeup() {
6828
7422
  if (osPlatform() === "darwin") {
6829
7423
  await execa("launchctl", ["unload", PLIST_PATH]).catch(() => {
6830
7424
  });
6831
- await fs23.remove(PLIST_PATH).catch(() => {
7425
+ await fs25.remove(PLIST_PATH).catch(() => {
6832
7426
  });
6833
7427
  success("launchd \uC2A4\uCF00\uC904 \uC81C\uAC70");
6834
7428
  console.log("");
@@ -6849,23 +7443,23 @@ async function disableWakeup() {
6849
7443
  } else {
6850
7444
  await removeCronEntry();
6851
7445
  }
6852
- await fs23.remove(CONFIG_FILE2).catch(() => {
7446
+ await fs25.remove(CONFIG_FILE2).catch(() => {
6853
7447
  });
6854
7448
  console.log("");
6855
7449
  success("\u2600\uFE0F \uC6E8\uC774\uD06C\uC5C5 \uD574\uC81C \uC644\uB8CC");
6856
7450
  }
6857
7451
  async function showStatus() {
6858
- if (await fs23.pathExists(CONFIG_FILE2)) {
6859
- const config = await fs23.readJson(CONFIG_FILE2);
7452
+ if (await fs25.pathExists(CONFIG_FILE2)) {
7453
+ const config = await fs25.readJson(CONFIG_FILE2);
6860
7454
  console.log("");
6861
7455
  success("\u2600\uFE0F \uC6E8\uC774\uD06C\uC5C5 \uD65C\uC131\uD654");
6862
- console.log(` \uC2DC\uAC04 ${chalk7.white(config.time)}`);
6863
- console.log(` \uC2A4\uCF00\uC904 ${chalk7.white(scheduleToLabel(config.schedule))}`);
6864
- console.log(` \uD504\uB85C\uC81D\uD2B8 ${chalk7.white(config.projectDir)}`);
6865
- console.log(` \uBAA8\uB4DC ${chalk7.white(config.launchMode === "yolo" ? "claude-YOLO mode" : "\uC77C\uBC18 \uBAA8\uB4DC")}`);
7456
+ console.log(` \uC2DC\uAC04 ${chalk8.white(config.time)}`);
7457
+ console.log(` \uC2A4\uCF00\uC904 ${chalk8.white(scheduleToLabel(config.schedule))}`);
7458
+ console.log(` \uD504\uB85C\uC81D\uD2B8 ${chalk8.white(config.projectDir)}`);
7459
+ console.log(` \uBAA8\uB4DC ${chalk8.white(config.launchMode === "yolo" ? "claude-YOLO mode" : "\uC77C\uBC18 \uBAA8\uB4DC")}`);
6866
7460
  if (osPlatform() === "darwin") {
6867
- const plistExists = await fs23.pathExists(PLIST_PATH);
6868
- console.log(` launchd ${plistExists ? chalk7.green("\uD65C\uC131") : chalk7.red("\uBE44\uD65C\uC131")}`);
7461
+ const plistExists = await fs25.pathExists(PLIST_PATH);
7462
+ console.log(` launchd ${plistExists ? chalk8.green("\uD65C\uC131") : chalk8.red("\uBE44\uD65C\uC131")}`);
6869
7463
  }
6870
7464
  console.log("");
6871
7465
  } else {
@@ -7017,7 +7611,7 @@ fi
7017
7611
 
7018
7612
  echo "[$(date)] PAI Wakeup completed" >> "$LOG_FILE"
7019
7613
  `;
7020
- await fs23.writeFile(SCRIPT_FILE, script, { mode: 493 });
7614
+ await fs25.writeFile(SCRIPT_FILE, script, { mode: 493 });
7021
7615
  }
7022
7616
  var PAI_DIR2, CONFIG_FILE2, MESSAGES_FILE, SCRIPT_FILE, PLIST_NAME, PLIST_PATH, CRON_MARKER, MESSAGES;
7023
7617
  var init_wakeup_cmd = __esm({
@@ -7158,6 +7752,10 @@ function createProgram() {
7158
7752
  const { removeCommand: removeCommand2 } = await Promise.resolve().then(() => (init_remove_cmd(), remove_cmd_exports));
7159
7753
  await removeCommand2(process.cwd(), options);
7160
7754
  });
7755
+ program2.command("fetch [recipe]").description("\uC678\uBD80 GitHub \uC800\uC7A5\uC18C\uC5D0\uC11C \uB808\uC2DC\uD53C(OAuth, \uCC57\uBD07 \uB4F1) \uB2E4\uC6B4\uB85C\uB4DC").option("--list", "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uB808\uC2DC\uD53C \uBAA9\uB85D").option("--all", "\uB4F1\uB85D\uB41C \uBAA8\uB4E0 \uB808\uC2DC\uD53C \uC124\uCE58").option("--overwrite", "\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD30C\uC77C \uB36E\uC5B4\uC4F0\uAE30").action(async (recipe, options) => {
7756
+ const { fetchCommand: fetchCommand2 } = await Promise.resolve().then(() => (init_fetch_cmd(), fetch_cmd_exports));
7757
+ await fetchCommand2(process.cwd(), recipe, options);
7758
+ });
7161
7759
  program2.command("upgrade").description("CLI + \uD504\uB85C\uC81D\uD2B8 \uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC\uB97C \uCD5C\uC2E0 \uBC84\uC804\uC73C\uB85C \uC5C5\uADF8\uB808\uC774\uB4DC").option("--force", "\uBC84\uC804 \uCCB4\uD06C \uBB34\uC2DC\uD558\uACE0 \uAC15\uC81C \uC5C5\uADF8\uB808\uC774\uB4DC").option("--skip-cli", "CLI \uC5C5\uADF8\uB808\uC774\uB4DC \uC0DD\uB7B5 (\uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C\uB9CC)").option("--skip-project", "\uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C \uC5C5\uADF8\uB808\uC774\uB4DC \uC0DD\uB7B5 (CLI\uB9CC)").action(async (options) => {
7162
7760
  const { upgradeCommand: upgradeCommand2 } = await Promise.resolve().then(() => (init_upgrade_cmd(), upgrade_cmd_exports));
7163
7761
  await upgradeCommand2(process.cwd(), options);