pai-zero 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/pai.js CHANGED
@@ -890,20 +890,22 @@ function generateClaudeMd(cwd, analysis, interview) {
890
890
 
891
891
  ## Project Overview
892
892
 
893
- 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\`
894
896
 
895
897
  <pai>
896
898
  ## PAI Configuration
897
899
 
898
900
  This project uses PAI (Plugin-based AI) for structured AI development.
899
901
 
900
- ### Pipeline: \uC124\uACC4-\uC2E4\uD589-\uAC80\uC99D-\uD3C9\uAC00-\uD658\uACBD
902
+ ### Pipeline: Environment \u2192 Design \u2192 Execution \u2192 Validation \u2192 Evaluation
901
903
 
902
- 1. **Environment** \u2014 \uD504\uB85C\uC81D\uD2B8 \uD658\uACBD \uAD6C\uC131 (\uC774 \uB2E8\uACC4)
903
- 2. **Design** \u2014 OpenSpec/OMC \uAE30\uBC18 \uC124\uACC4
904
- 3. **Execution** \u2014 AI \uCF54\uB4DC \uC0DD\uC131
905
- 4. **Validation** \u2014 gstack \uD14C\uC2A4\uD2B8 / harness \uAC80\uC99D
906
- 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
907
909
 
908
910
  ### Stack
909
911
  - **Languages**: ${stack}
@@ -911,15 +913,30 @@ This project uses PAI (Plugin-based AI) for structured AI development.
911
913
  - **Mode**: ${interview.mode}
912
914
  - **Config**: \`.pai/config.json\`
913
915
 
914
- 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
915
925
 
916
926
  ### Global Rules
917
927
 
918
- - **\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.
919
- - **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.
920
- - **\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.
921
- - **\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.
922
- - **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
923
940
 
924
941
  ### Contribution Guide
925
942
 
@@ -2070,34 +2087,33 @@ async function provisionGitHub(ctx) {
2070
2087
  await Promise.all(dirs.map((d) => fs7.ensureDir(join4(ctx.cwd, d))));
2071
2088
  const handoffPath = join4(ctx.cwd, "handoff.md");
2072
2089
  if (!await fs7.pathExists(handoffPath)) {
2073
- const now = (/* @__PURE__ */ new Date()).toLocaleString("ko-KR");
2090
+ const now2 = (/* @__PURE__ */ new Date()).toLocaleString("ko-KR");
2074
2091
  await fs7.writeFile(handoffPath, [
2075
2092
  `# Handoff \u2014 ${ctx.projectName}`,
2076
2093
  "",
2077
- `> \uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8: ${now}`,
2094
+ `> \uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8: ${now2}`,
2095
+ `> **\uCC38\uACE0**: \`<!-- roboco:start -->\` \uBE14\uB85D\uC740 \uD30C\uC774\uD504\uB77C\uC778 \uC9C4\uD589 \uC2DC \uC790\uB3D9 \uAC31\uC2E0\uB429\uB2C8\uB2E4.`,
2096
+ `> \uADF8 \uC678 \uC139\uC158(\uB2E4\uC74C \uB2E8\uACC4, \uBA54\uBAA8)\uC740 \uC790\uC720\uB86D\uAC8C \uC218\uB3D9 \uD3B8\uC9D1 \uAC00\uB2A5.`,
2078
2097
  "",
2079
- "## \uC9C4\uD589 \uC911",
2080
- "- [ ] Ideation \uBB38\uC11C \uC791\uC131 \u2014 \uC2EC\uCE35 \uC778\uD130\uBDF0\uB97C \uD1B5\uD574 \uC544\uC774\uB514\uC5B4\uB97C \uAD6C\uCCB4\uD654 (\uB2F4\uB2F9: PAI > Design)",
2098
+ "<!-- roboco:start -->",
2099
+ "## \uD30C\uC774\uD504\uB77C\uC778 \uC9C4\uD589 \uC0C1\uD0DC (roboco \uC790\uB3D9 \uAD00\uB9AC)",
2081
2100
  "",
2082
- "## \uB2E4\uC74C \uB2E8\uACC4",
2083
- "- [ ] Ideation \uAE30\uBC18 PRD \uC791\uC131 \u2014 \uC2EC\uCE35 \uC778\uD130\uBDF0\uB85C \uB9E5\uB77D/\uAE30\uC220 \uC81C\uC57D \uBCF4\uAC15",
2084
- "- [ ] \uD558\uB124\uC2A4 \uC5D4\uC9C0\uB2C8\uC5B4\uB9C1 \uAD6C\uC131 \uD655\uC778 \u2014 \uC5D0\uC774\uC804\uD2B8 \uC791\uC5C5 \uD658\uACBD \uAC80\uC99D",
2085
- "- [ ] AI \uCF54\uB4DC \uC0DD\uC131 \uC2DC\uC791",
2101
+ "\uD604\uC7AC \uB2E8\uACC4: **environment**",
2086
2102
  "",
2087
- "## \uC644\uB8CC",
2088
- `- [x] \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654 (${now}) (\uB2F4\uB2F9: PAI > Environment)`,
2103
+ "### \uC644\uB8CC",
2104
+ `- [x] environment (pai init, ${now2.slice(0, 10)})`,
2105
+ "<!-- roboco:end -->",
2089
2106
  "",
2090
- "## \uC791\uC5C5 \uD30C\uC774\uD504\uB77C\uC778",
2091
- "```",
2092
- "1. /pai design \u2192 Ideation \uC2EC\uCE35 \uC778\uD130\uBDF0 \u2192 docs/ideation.md \uC0DD\uC131",
2093
- "2. /pai design \u2192 PRD \uC2EC\uCE35 \uC778\uD130\uBDF0 \u2192 docs/openspec.md \uC791\uC131",
2094
- "3. /pai design \u2192 \uAE30\uC220 \uC81C\uC57D \uC2EC\uCE35 \uC778\uD130\uBDF0 \u2192 \uD558\uB124\uC2A4 \uC5D4\uC9C0\uB2C8\uC5B4\uB9C1 \uD655\uC778",
2095
- "4. AI \uCF54\uB4DC \uC0DD\uC131 \u2192 \uAC80\uC99D \u2192 \uD3C9\uAC00",
2096
- "```",
2107
+ "## \uB2E4\uC74C \uB2E8\uACC4",
2108
+ "- [ ] `/pai design` \u2014 Ideation + PRD \uC2EC\uCE35 \uC778\uD130\uBDF0",
2109
+ "- [ ] Claude Code\uB85C \uCF54\uB4DC \uC0DD\uC131",
2110
+ "- [ ] `pai test` \u2014 \uD14C\uC2A4\uD2B8 + \uD558\uB124\uC2A4 \uAC80\uC99D",
2111
+ "- [ ] `pai grade` \u2014 AI \uC900\uBE44\uB3C4 6\uCE74\uD14C\uACE0\uB9AC \uD3C9\uAC00",
2097
2112
  "",
2098
2113
  "## \uBA54\uBAA8",
2099
2114
  `- \uBAA8\uB4DC: ${ctx.mode}`,
2100
- "- \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uBD80\uC871\uD558\uBA74 AI\uAC00 \uC790\uB3D9\uC73C\uB85C \uC2EC\uCE35 \uC778\uD130\uBDF0\uB97C \uC9C4\uD589\uD569\uB2C8\uB2E4"
2115
+ "- \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uBD80\uC871\uD558\uBA74 AI\uAC00 \uC790\uB3D9\uC73C\uB85C \uC2EC\uCE35 \uC778\uD130\uBDF0\uB97C \uC9C4\uD589\uD569\uB2C8\uB2E4",
2116
+ "- \uB77D \uCDA9\uB3CC \uC2DC \uC989\uC2DC \uC2E4\uD328 (\uB2E4\uB978 PAI \uBA85\uB839\uC774 \uC9C4\uD589 \uC911\uC774\uBA74 \uC644\uB8CC \uD6C4 \uC7AC\uC2DC\uB3C4)"
2101
2117
  ].join("\n") + "\n");
2102
2118
  }
2103
2119
  const contribPath = join4(ctx.cwd, "CONTRIBUTING.md");
@@ -2244,54 +2260,189 @@ async function provisionSupabase(ctx) {
2244
2260
  async function provisionOpenSpec(ctx) {
2245
2261
  await fs7.ensureDir(join4(ctx.cwd, "docs"));
2246
2262
  const openspecPath = join4(ctx.cwd, "docs", "openspec.md");
2247
- if (!await fs7.pathExists(openspecPath)) {
2248
- await fs7.writeFile(openspecPath, [
2249
- `# OpenSpec \u2014 ${ctx.projectName}`,
2250
- "",
2251
- "## 1. \uBAA9\uC801 (Purpose)",
2252
- "> \uC774 \uC11C\uBE44\uC2A4\uB294 \uBB34\uC5C7\uC744 \uD574\uACB0\uD558\uB294\uAC00?",
2253
- "",
2254
- "## 2. \uC0AC\uC6A9\uC790 (Users)",
2255
- "| \uC5ED\uD560 | \uC124\uBA85 |",
2256
- "|------|------|",
2257
- "| - | - |",
2258
- "",
2259
- "## 3. \uD575\uC2EC \uAE30\uB2A5 (Features)",
2260
- "- [ ] Feature 1",
2263
+ if (await fs7.pathExists(openspecPath)) return;
2264
+ const langs = ctx.analysis?.stack.languages ?? [];
2265
+ const frameworks = ctx.analysis?.stack.frameworks ?? [];
2266
+ const pkgMgr = ctx.analysis?.stack.packageManager;
2267
+ const frontendFrameworks = frameworks.filter(
2268
+ (f) => ["React", "Vue", "Next.js", "Nuxt", "Svelte", "Angular"].includes(f)
2269
+ );
2270
+ const backendFrameworks = frameworks.filter(
2271
+ (f) => ["Express", "Fastify", "NestJS", "Koa", "Django", "Flask", "FastAPI", "Rails", "Spring"].includes(f)
2272
+ );
2273
+ const frontendLine = frontendFrameworks.length > 0 ? frontendFrameworks.join(" + ") : "<!-- TODO: \uD504\uB860\uD2B8\uC5D4\uB4DC \uD504\uB808\uC784\uC6CC\uD06C (React / Vue / Next.js \uB4F1) -->";
2274
+ 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 -->";
2275
+ 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.";
2276
+ await fs7.writeFile(openspecPath, [
2277
+ `# OpenSpec \u2014 ${ctx.projectName}`,
2278
+ "",
2279
+ `> **\uBAA8\uB4DC**: ${ctx.mode} \xB7 ${modeHint}`,
2280
+ `> **\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.`,
2281
+ `> **\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.`,
2282
+ "",
2283
+ "---",
2284
+ "",
2285
+ "## 1. \uBAA9\uC801 (Purpose)",
2286
+ "",
2287
+ "<!-- TODO: \uC774 \uC11C\uBE44\uC2A4\uAC00 \uBB34\uC5C7\uC744 \uD574\uACB0\uD558\uB294\uC9C0 2-3\uBB38\uC7A5\uC73C\uB85C \uAE30\uC220 -->",
2288
+ "<!-- \uC608\uC2DC:",
2289
+ " - \uB204\uAD6C\uC758 \uC5B4\uB5A4 \uBB38\uC81C\uB97C \uD574\uACB0\uD558\uB294\uAC00?",
2290
+ " - \uAE30\uC874 \uB300\uC548(\uC788\uB2E4\uBA74) \uB300\uBE44 \uCC28\uBCC4\uC810\uC740?",
2291
+ "-->",
2292
+ "",
2293
+ "## 2. \uC0AC\uC6A9\uC790 (Users)",
2294
+ "",
2295
+ "<!-- TODO: \uC2E4\uC81C \uC0AC\uC6A9\uC790 \uC5ED\uD560\uC744 \uC815\uC758 \u2014 role, \uAD8C\uD55C, \uC8FC\uC694 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 -->",
2296
+ "",
2297
+ "| \uC5ED\uD560 | \uAD8C\uD55C | \uC8FC\uC694 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 |",
2298
+ "|------|------|-------------------|",
2299
+ "| \uC77C\uBC18 \uC0AC\uC6A9\uC790 | \uC77D\uAE30/\uC4F0\uAE30 | (\uC608: \uAC8C\uC2DC\uAE00 \uC791\uC131, \uAC80\uC0C9) |",
2300
+ "| \uAD00\uB9AC\uC790 | \uC804\uCCB4 | (\uC608: \uC0AC\uC6A9\uC790 \uAD00\uB9AC, \uD1B5\uACC4 \uC870\uD68C) |",
2301
+ "",
2302
+ "## 3. \uD575\uC2EC \uAE30\uB2A5 (Features)",
2303
+ "",
2304
+ "<!-- TODO: MVP \uAE30\uB2A5 \uBAA9\uB85D. \uCCB4\uD06C\uBC15\uC2A4\uB85C \uC9C4\uD589\uC0C1\uD0DC \uD45C\uC2DC -->",
2305
+ "",
2306
+ "- [ ] \uC0AC\uC6A9\uC790 \uC778\uC99D (\uB85C\uADF8\uC778/\uB85C\uADF8\uC544\uC6C3)",
2307
+ "- [ ] \uD575\uC2EC \uAE30\uB2A5 1",
2308
+ "- [ ] \uD575\uC2EC \uAE30\uB2A5 2",
2309
+ "",
2310
+ "## 4. \uAE30\uC220 \uC2A4\uD0DD (Stack)",
2311
+ "",
2312
+ `- **Language**: ${langs.length > 0 ? langs.join(", ") : "<!-- TODO: \uC5B8\uC5B4 -->"}`,
2313
+ `- **Frontend**: ${frontendLine}`,
2314
+ `- **Backend**: ${backendLine}`,
2315
+ `- **Database**: <!-- TODO: PostgreSQL / MySQL / SQLite / MongoDB \uB4F1 -->`,
2316
+ `- **Package Manager**: ${pkgMgr ?? "<!-- TODO -->"}`,
2317
+ `- **Deployment**: <!-- TODO: Vercel / AWS / Docker \uB4F1 -->`,
2318
+ "",
2319
+ "## 5. API \uC5D4\uB4DC\uD3EC\uC778\uD2B8 (Endpoints)",
2320
+ "",
2321
+ "<!-- \uC911\uC694: \uC774 \uD45C\uAC00 gstack \uD14C\uC2A4\uD2B8 \uD0C0\uAC9F + harness \uAC80\uC99D \uD0C0\uAC9F\uC785\uB2C8\uB2E4.",
2322
+ " \uC2E0\uADDC \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uCD94\uAC00 \uC2DC \uBC18\uB4DC\uC2DC \uC5EC\uAE30 \uBA3C\uC800 \uC120\uC5B8. -->",
2323
+ "",
2324
+ "| Method | Path | \uC124\uBA85 | \uC694\uCCAD \uBAA8\uB378 | \uC751\uB2F5 \uBAA8\uB378 |",
2325
+ "|--------|------|------|-----------|-----------|",
2326
+ "| GET | /api/health | \uD5EC\uC2A4\uCCB4\uD06C | - | `{ ok: true }` |",
2327
+ "| POST | /api/auth/login | \uB85C\uADF8\uC778 | `{ email, password }` | `{ token }` |",
2328
+ "<!-- TODO: \uD504\uB85C\uC81D\uD2B8 \uACE0\uC720 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uCD94\uAC00 -->",
2329
+ "",
2330
+ "## 6. \uBE44\uAE30\uB2A5 \uC694\uAD6C\uC0AC\uD56D (\uC120\uD0DD)",
2331
+ "",
2332
+ "<!-- TODO: \uC131\uB2A5/\uBCF4\uC548/\uD655\uC7A5\uC131 \uC694\uAD6C \u2014 production \uBAA8\uB4DC\uC5D0\uC11C \uAD8C\uC7A5 -->",
2333
+ "",
2334
+ "---",
2335
+ "",
2336
+ "## \uD30C\uC774\uD504\uB77C\uC778 \uACC4\uC57D (\uC774 \uBB38\uC11C\uAC00 \uCC38\uC870\uB418\uB294 \uACF3)",
2337
+ "",
2338
+ "- `harness.json` \u2192 `specFile`\uB85C \uC774 \uBB38\uC11C \uCC38\uC870 (\uC124\uACC4\u2194\uAD6C\uD604 \uC77C\uCE58 \uAC80\uC99D)",
2339
+ "- `gstack.json` \u2192 \uAC01 \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uC5D0 \uB300\uD55C \uD14C\uC2A4\uD2B8 \uC874\uC7AC \uD655\uC778",
2340
+ "- `.pai/omc.md` \u2192 \uB3C4\uBA54\uC778 \uAC1D\uCCB4\uAC00 \uC694\uCCAD/\uC751\uB2F5 \uBAA8\uB378\uACFC \uC815\uD569\uD558\uB294\uC9C0 \uD655\uC778",
2341
+ ""
2342
+ ].join("\n") + "\n");
2343
+ }
2344
+ function buildOMCSkeleton(projectName, mode) {
2345
+ const lines = [
2346
+ `# OMC \u2014 Object Model Context (${projectName})`,
2347
+ "",
2348
+ `> **\uBAA8\uB4DC**: ${mode}`,
2349
+ "> **\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.",
2350
+ "> **\uD30C\uC774\uD504\uB77C\uC778 \uACC4\uC57D**:",
2351
+ "> - \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",
2352
+ "> - `.pai/harness.json`\uC758 `businessRules` \uAC80\uC99D\uC774 \uC774 \uBB38\uC11C\uB97C \uCC38\uC870",
2353
+ "> **\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.",
2354
+ "",
2355
+ "---",
2356
+ "",
2357
+ "## \uB3C4\uBA54\uC778 \uAC1D\uCCB4",
2358
+ "",
2359
+ "### User",
2360
+ "",
2361
+ "```ts",
2362
+ "interface User {",
2363
+ " id: string;",
2364
+ " email: string;",
2365
+ " name: string;",
2366
+ " role: 'admin' | 'member';",
2367
+ " createdAt: Date;",
2368
+ "}",
2369
+ "```",
2370
+ "",
2371
+ "<!-- 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 -->",
2372
+ ""
2373
+ ];
2374
+ if (mode === "poc" || mode === "production") {
2375
+ lines.push(
2376
+ "### Session",
2261
2377
  "",
2262
- "## 4. \uAE30\uC220 \uC2A4\uD0DD (Stack)",
2263
- "- Frontend:",
2264
- "- Backend:",
2265
- "- DB:",
2378
+ "```ts",
2379
+ "interface Session {",
2380
+ " token: string;",
2381
+ " userId: string;",
2382
+ " expiresAt: Date;",
2383
+ "}",
2384
+ "```",
2385
+ ""
2386
+ );
2387
+ }
2388
+ if (mode === "production") {
2389
+ lines.push(
2390
+ "### AuditLog",
2266
2391
  "",
2267
- "## 5. API \uC5D4\uB4DC\uD3EC\uC778\uD2B8 (Endpoints)",
2268
- "| Method | Path | \uC124\uBA85 |",
2269
- "|--------|------|------|",
2270
- "| GET | /api/health | \uD5EC\uC2A4\uCCB4\uD06C |"
2271
- ].join("\n") + "\n");
2392
+ "```ts",
2393
+ "interface AuditLog {",
2394
+ " id: string;",
2395
+ " userId: string;",
2396
+ " action: 'create' | 'update' | 'delete' | 'read';",
2397
+ " resource: string;",
2398
+ " at: Date;",
2399
+ " metadata?: Record<string, unknown>;",
2400
+ "}",
2401
+ "```",
2402
+ ""
2403
+ );
2404
+ }
2405
+ lines.push(
2406
+ "## \uBE44\uC988\uB2C8\uC2A4 \uADDC\uCE59",
2407
+ "",
2408
+ '<!-- \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. -->',
2409
+ "",
2410
+ "- **\uC778\uC99D \uD544\uC218**: `User.id`\uAC00 \uC5C6\uB294 \uC694\uCCAD\uC740 \uAD00\uB9AC\uC790 API \uC811\uADFC \uBD88\uAC00",
2411
+ "- **\uC774\uBA54\uC77C \uC720\uC77C\uC131**: `User.email`\uC740 \uC2DC\uC2A4\uD15C \uC804\uCCB4\uC5D0\uC11C \uC720\uC77C"
2412
+ );
2413
+ if (mode === "poc" || mode === "production") {
2414
+ lines.push(
2415
+ "- **\uC138\uC158 \uB9CC\uB8CC**: `Session.expiresAt < now()` \uC774\uBA74 \uBAA8\uB4E0 API 401"
2416
+ );
2272
2417
  }
2418
+ if (mode === "production") {
2419
+ lines.push(
2420
+ "- **\uAC10\uC0AC \uB85C\uADF8**: \uBAA8\uB4E0 \uC4F0\uAE30 \uC561\uC158(create/update/delete)\uC740 `AuditLog`\uC5D0 \uAE30\uB85D",
2421
+ '- **\uAD8C\uD55C \uBD84\uB9AC**: `role === "member"` \uC0AC\uC6A9\uC790\uB294 \uB2E4\uB978 \uC0AC\uC6A9\uC790\uC758 \uB9AC\uC18C\uC2A4 \uC218\uC815 \uBD88\uAC00'
2422
+ );
2423
+ }
2424
+ lines.push(
2425
+ "",
2426
+ "<!-- 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. -->",
2427
+ "",
2428
+ "## \uB3C4\uBA54\uC778 - API \uB9E4\uD551 (\uC120\uD0DD)",
2429
+ "",
2430
+ "<!-- openspec.md \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uC640 \uB3C4\uBA54\uC778 \uAC1D\uCCB4 \uC5F0\uACB0. harness \uAC80\uC99D \uC6A9. -->",
2431
+ "",
2432
+ "| Endpoint | \uC694\uCCAD \uBAA8\uB378 | \uC751\uB2F5 \uBAA8\uB378 | \uBE44\uACE0 |",
2433
+ "|----------|-----------|-----------|------|",
2434
+ "| POST /api/auth/login | `{ email, password }` | `Session` | \uC131\uACF5 \uC2DC token \uBC1C\uAE09 |",
2435
+ "<!-- TODO: \uCD94\uAC00 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uB9E4\uD551 -->",
2436
+ ""
2437
+ );
2438
+ return lines.join("\n") + "\n";
2273
2439
  }
2274
2440
  async function provisionOMC(ctx) {
2275
2441
  await fs7.ensureDir(join4(ctx.cwd, ".pai"));
2276
2442
  const omcPath = join4(ctx.cwd, ".pai", "omc.md");
2277
2443
  if (!await fs7.pathExists(omcPath)) {
2278
- await fs7.writeFile(omcPath, [
2279
- `# OMC \u2014 Object Model Context (${ctx.projectName})`,
2280
- "",
2281
- "> AI\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uB3C4\uBA54\uC778\uC744 \uC774\uD574\uD558\uAE30 \uC704\uD55C \uD575\uC2EC \uAC1D\uCCB4 \uBAA8\uB378",
2282
- "",
2283
- "## \uB3C4\uBA54\uC778 \uAC1D\uCCB4",
2284
- "",
2285
- "### User",
2286
- "```",
2287
- "id: string",
2288
- "email: string",
2289
- 'role: "admin" | "member"',
2290
- "```",
2291
- "",
2292
- "## \uBE44\uC988\uB2C8\uC2A4 \uADDC\uCE59",
2293
- "- (\uC5EC\uAE30\uC5D0 \uD575\uC2EC \uBE44\uC988\uB2C8\uC2A4 \uB85C\uC9C1\uC744 \uAE30\uC220)"
2294
- ].join("\n") + "\n");
2444
+ const skeleton = buildOMCSkeleton(ctx.projectName, ctx.mode);
2445
+ await fs7.writeFile(omcPath, skeleton);
2295
2446
  }
2296
2447
  const omcDir = join4(ctx.cwd, ".omc");
2297
2448
  await fs7.ensureDir(omcDir);
@@ -2333,13 +2484,20 @@ async function provisionGstack(ctx) {
2333
2484
  }
2334
2485
  async function provisionRoboco(ctx) {
2335
2486
  await fs7.ensureDir(join4(ctx.cwd, ".pai"));
2336
- const robocoPath = join4(ctx.cwd, ".pai", "roboco.json");
2337
- if (await fs7.pathExists(robocoPath)) return;
2338
- await fs7.writeJson(robocoPath, {
2339
- version: "1.0",
2340
- checks: ["github", "vercel", "supabase", "openspec", "omc"],
2341
- reportOutput: "AI_READINESS_REPORT.md",
2342
- note: "PAI Zero\uC5D0 \uB0B4\uC7AC\uD654\uB428. `pai evaluate`\uB85C \uC9C4\uB2E8 \uAC00\uB2A5."
2487
+ const robocoPath2 = join4(ctx.cwd, ".pai", "roboco.json");
2488
+ if (await fs7.pathExists(robocoPath2)) {
2489
+ try {
2490
+ const existing = await fs7.readJson(robocoPath2);
2491
+ if (existing?.version === "2.0") return;
2492
+ } catch {
2493
+ }
2494
+ }
2495
+ await fs7.writeJson(robocoPath2, {
2496
+ version: "2.0",
2497
+ currentStage: "environment",
2498
+ stageHistory: [],
2499
+ lock: null,
2500
+ gates: {}
2343
2501
  }, { spaces: 2 });
2344
2502
  }
2345
2503
  async function provisionHarness(ctx) {
@@ -3263,6 +3421,183 @@ description: "\uD50C\uB7EC\uADF8\uC778 \uCD94\uAC00 \uC124\uCE58 \u2014 Mockup\u
3263
3421
  }
3264
3422
  });
3265
3423
 
3424
+ // src/core/detector.ts
3425
+ var detector_exports = {};
3426
+ __export(detector_exports, {
3427
+ PLUGIN_META: () => PLUGIN_META,
3428
+ PLUGIN_SIGNATURES: () => PLUGIN_SIGNATURES,
3429
+ deriveEdges: () => deriveEdges,
3430
+ getBasePluginsForMode: () => getBasePluginsForMode,
3431
+ getPluginsForMode: () => getPluginsForMode,
3432
+ nextMode: () => nextMode,
3433
+ pluginsAddedByPromotion: () => pluginsAddedByPromotion,
3434
+ scanProjectState: () => scanProjectState
3435
+ });
3436
+ import path4 from "path";
3437
+ import fs9 from "fs-extra";
3438
+ async function scanProjectState(cwd) {
3439
+ const result = {
3440
+ isNewProject: true,
3441
+ hasPaiConfig: false,
3442
+ projectMode: null,
3443
+ installedPlugins: [],
3444
+ missingPlugins: [],
3445
+ details: {}
3446
+ };
3447
+ const paiConfigPath = path4.join(cwd, ".pai", "config.json");
3448
+ if (await fs9.pathExists(paiConfigPath)) {
3449
+ result.hasPaiConfig = true;
3450
+ try {
3451
+ const config = await fs9.readJson(paiConfigPath);
3452
+ result.projectMode = config.mode != null ? normalizeMode(config.mode) : null;
3453
+ } catch {
3454
+ }
3455
+ }
3456
+ for (const [key, signatures] of Object.entries(PLUGIN_SIGNATURES)) {
3457
+ const installed = await Promise.any(
3458
+ signatures.map(async (sig) => {
3459
+ if (await fs9.pathExists(path4.join(cwd, sig))) return true;
3460
+ throw new Error("not found");
3461
+ })
3462
+ ).catch(() => false);
3463
+ result.details[key] = { installed, signatures };
3464
+ if (installed) {
3465
+ result.installedPlugins.push(key);
3466
+ } else {
3467
+ result.missingPlugins.push(key);
3468
+ }
3469
+ }
3470
+ 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"));
3471
+ result.isNewProject = !hasAnyContent;
3472
+ return result;
3473
+ }
3474
+ function getPluginsForMode(mode) {
3475
+ return Object.entries(PLUGIN_META).filter(([, meta]) => meta.modes.includes(mode)).map(([key, meta]) => ({ key, ...meta }));
3476
+ }
3477
+ function getBasePluginsForMode(mode) {
3478
+ return Object.entries(PLUGIN_META).filter(([, meta]) => meta.modes.includes(mode) && meta.required).map(([key]) => key);
3479
+ }
3480
+ function nextMode(current) {
3481
+ if (current === "prototype") return "poc";
3482
+ if (current === "poc") return "production";
3483
+ return null;
3484
+ }
3485
+ function pluginsAddedByPromotion(from, to) {
3486
+ const fromSet = new Set(getPluginsForMode(from).map((p) => p.key));
3487
+ return getPluginsForMode(to).map((p) => p.key).filter((k) => !fromSet.has(k));
3488
+ }
3489
+ function deriveEdges(installedPlugins) {
3490
+ const has = new Set(installedPlugins);
3491
+ const edges = [];
3492
+ if (has.has("harness") && has.has("openspec")) {
3493
+ edges.push({
3494
+ from: "harness",
3495
+ to: "openspec",
3496
+ via: "specFile",
3497
+ path: "docs/openspec.md",
3498
+ description: "harness\uAC00 openspec\uC758 API \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uBAA9\uB85D\uC744 \uC77D\uC5B4 \uAD6C\uD604 \uC77C\uCE58 \uAC80\uC99D"
3499
+ });
3500
+ }
3501
+ if (has.has("gstack") && has.has("openspec")) {
3502
+ edges.push({
3503
+ from: "gstack",
3504
+ to: "openspec",
3505
+ via: "endpoints",
3506
+ path: "docs/openspec.md",
3507
+ 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"
3508
+ });
3509
+ }
3510
+ if (has.has("omc") && has.has("openspec")) {
3511
+ edges.push({
3512
+ from: "omc",
3513
+ to: "openspec",
3514
+ via: "domain",
3515
+ path: ".pai/omc.md",
3516
+ description: "omc\uC758 \uB3C4\uBA54\uC778 \uAC1D\uCCB4\uAC00 openspec\uC758 API \uC694\uCCAD/\uC751\uB2F5 \uBAA8\uB378\uACFC \uC815\uD569"
3517
+ });
3518
+ }
3519
+ if (has.has("harness") && has.has("omc")) {
3520
+ edges.push({
3521
+ from: "harness",
3522
+ to: "omc",
3523
+ via: "businessRules",
3524
+ path: ".pai/omc.md",
3525
+ description: "harness\uAC00 omc\uC758 \uBE44\uC988\uB2C8\uC2A4 \uADDC\uCE59\uC744 \uAC80\uC99D \uADDC\uCE59\uC73C\uB85C \uCC38\uC870"
3526
+ });
3527
+ }
3528
+ return edges;
3529
+ }
3530
+ var PLUGIN_SIGNATURES, PLUGIN_META;
3531
+ var init_detector = __esm({
3532
+ "src/core/detector.ts"() {
3533
+ "use strict";
3534
+ init_config();
3535
+ PLUGIN_SIGNATURES = {
3536
+ github: [".git", ".github"],
3537
+ vercel: [".vercel", "vercel.json"],
3538
+ supabase: ["supabase", ".supabase"],
3539
+ openspec: ["openspec.md", "docs/openspec.md", ".pai/openspec.md"],
3540
+ omc: [".pai/omc.md", "omc.config.js", "omc.config.json"],
3541
+ gstack: [".pai/gstack.json", "gstack.config.js"],
3542
+ roboco: [".pai/roboco.json", "roboco.config.js", ".roboco"],
3543
+ harness: [".pai/harness.json", "harness.config.js", ".harness"]
3544
+ };
3545
+ PLUGIN_META = {
3546
+ github: {
3547
+ label: "GitHub \uB808\uD3EC & \uD3F4\uB354 \uAD6C\uC870",
3548
+ description: "\uB808\uD3EC \uCD08\uAE30\uD654, .gitignore, \uAE30\uBCF8 \uBE0C\uB79C\uCE58 \uC124\uC815",
3549
+ modes: ["prototype", "poc", "production"],
3550
+ required: true
3551
+ },
3552
+ openspec: {
3553
+ label: "OpenSpec (PRD \uC124\uACC4)",
3554
+ description: "AI \uAE30\uBC18 PRD \uC0DD\uC131 \uBC0F \uC2A4\uD399 \uBB38\uC11C\uD654",
3555
+ modes: ["prototype", "poc", "production"],
3556
+ required: true
3557
+ },
3558
+ roboco: {
3559
+ label: "roboco (AI \uC9C4\uB2E8)",
3560
+ description: "\uC124\uCE58 \uC0C1\uD0DC \uD3C9\uAC00 \uBC0F AI \uC900\uBE44\uB3C4 \uB9AC\uD3EC\uD2B8 \uC0DD\uC131",
3561
+ modes: ["prototype", "poc", "production"],
3562
+ required: false,
3563
+ url: "https://github.com/SoInKyu/roboco-cli"
3564
+ },
3565
+ omc: {
3566
+ label: "OMC (oh-my-claudecode)",
3567
+ description: "\uAC1D\uCCB4 \uBAA8\uB378 \uCEE8\uD14D\uC2A4\uD2B8 + Claude Code \uBA40\uD2F0 \uC5D0\uC774\uC804\uD2B8 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158",
3568
+ modes: ["poc", "production"],
3569
+ required: false,
3570
+ url: "https://github.com/SoInKyu/oh-my-claudecode"
3571
+ },
3572
+ vercel: {
3573
+ label: "Vercel \uBC30\uD3EC \uC5F0\uB3D9",
3574
+ description: "\uC790\uB3D9 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uBC0F Preview URL \uC124\uC815",
3575
+ modes: ["poc", "production"],
3576
+ required: false
3577
+ },
3578
+ gstack: {
3579
+ label: "gstack (QA / \uD488\uC9C8\uAD00\uB9AC)",
3580
+ description: "\uD14C\uC2A4\uD2B8 \uC790\uB3D9\uD654 \uBC0F \uD488\uC9C8 \uAE30\uC900 \uC124\uC815",
3581
+ modes: ["production"],
3582
+ required: false,
3583
+ url: "https://github.com/SoInKyu/gstack"
3584
+ },
3585
+ harness: {
3586
+ label: "Harness Engineering (\uAC80\uC99D \uC790\uB3D9\uD654)",
3587
+ description: "\uC124\uACC4(OpenSpec)\uC640 \uAD6C\uD604 \uC77C\uCE58 \uC5EC\uBD80 \uC790\uB3D9 \uCCB4\uD06C",
3588
+ modes: ["production"],
3589
+ required: false
3590
+ },
3591
+ supabase: {
3592
+ label: "Supabase (DB & Auth)",
3593
+ description: "\uB370\uC774\uD130\uBCA0\uC774\uC2A4, \uC778\uC99D, API \uD0A4 \uC790\uB3D9 \uC5F0\uB3D9",
3594
+ modes: ["production"],
3595
+ required: false
3596
+ }
3597
+ };
3598
+ }
3599
+ });
3600
+
3266
3601
  // src/stages/environment/doctor.ts
3267
3602
  var doctor_exports = {};
3268
3603
  __export(doctor_exports, {
@@ -3270,7 +3605,7 @@ __export(doctor_exports, {
3270
3605
  });
3271
3606
  import { join as join6 } from "path";
3272
3607
  import { homedir } from "os";
3273
- import fs9 from "fs-extra";
3608
+ import fs10 from "fs-extra";
3274
3609
  async function runDoctor() {
3275
3610
  section("PAI Doctor \u2014 \uD658\uACBD \uC9C4\uB2E8");
3276
3611
  const checks = [];
@@ -3290,7 +3625,7 @@ async function runDoctor() {
3290
3625
  fix: claudeCheck.ok ? void 0 : "npm install -g @anthropic-ai/claude-code"
3291
3626
  });
3292
3627
  const globalConfigPath = join6(homedir(), ".pai", "config.json");
3293
- const hasGlobalConfig = await fs9.pathExists(globalConfigPath);
3628
+ const hasGlobalConfig = await fs10.pathExists(globalConfigPath);
3294
3629
  checks.push({
3295
3630
  label: "\uAE00\uB85C\uBC8C \uC124\uC815",
3296
3631
  ok: true,
@@ -3346,8 +3681,8 @@ var init_doctor = __esm({
3346
3681
  });
3347
3682
 
3348
3683
  // src/utils/github-fetch.ts
3349
- import path4 from "path";
3350
- import fs10 from "fs-extra";
3684
+ import path5 from "path";
3685
+ import fs11 from "fs-extra";
3351
3686
  async function httpGet(url, timeoutMs, accept) {
3352
3687
  const controller = new AbortController();
3353
3688
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -3378,8 +3713,8 @@ async function downloadFile(downloadUrl, destPath, timeoutMs) {
3378
3713
  throw new Error(`Download failed ${res.status} ${res.statusText} (${downloadUrl})`);
3379
3714
  }
3380
3715
  const buf = Buffer.from(await res.arrayBuffer());
3381
- await fs10.ensureDir(path4.dirname(destPath));
3382
- await fs10.writeFile(destPath, buf);
3716
+ await fs11.ensureDir(path5.dirname(destPath));
3717
+ await fs11.writeFile(destPath, buf);
3383
3718
  }
3384
3719
  async function fetchGithubDir(opts) {
3385
3720
  const {
@@ -3403,10 +3738,10 @@ async function fetchGithubDir(opts) {
3403
3738
  for (const entry of entries) {
3404
3739
  const relName = entry.name;
3405
3740
  if (entry.type === "dir") {
3406
- await walk(`${currentSrc}/${relName}`, path4.join(currentDest, relName));
3741
+ await walk(`${currentSrc}/${relName}`, path5.join(currentDest, relName));
3407
3742
  } else if (entry.type === "file") {
3408
- const destFile = path4.join(currentDest, relName);
3409
- if (!overwrite && await fs10.pathExists(destFile)) {
3743
+ const destFile = path5.join(currentDest, relName);
3744
+ if (!overwrite && await fs11.pathExists(destFile)) {
3410
3745
  result.skipped.push(destFile);
3411
3746
  continue;
3412
3747
  }
@@ -3438,8 +3773,8 @@ var fetch_cmd_exports = {};
3438
3773
  __export(fetch_cmd_exports, {
3439
3774
  fetchCommand: () => fetchCommand
3440
3775
  });
3441
- import path5 from "path";
3442
- import fs11 from "fs-extra";
3776
+ import path6 from "path";
3777
+ import fs12 from "fs-extra";
3443
3778
  import chalk4 from "chalk";
3444
3779
  async function fetchCommand(cwd, recipeKey, options) {
3445
3780
  if (options.list) {
@@ -3477,7 +3812,7 @@ async function fetchCommand(cwd, recipeKey, options) {
3477
3812
  const installed = [];
3478
3813
  for (const key of keys) {
3479
3814
  const recipe = RECIPES[key];
3480
- const targetDir = path5.join(cwd, recipe.target);
3815
+ const targetDir = path6.join(cwd, recipe.target);
3481
3816
  console.log("");
3482
3817
  console.log(` ${colors.accent(key)} \u2014 ${recipe.label}`);
3483
3818
  const result = await withSpinner(`\uB2E4\uC6B4\uB85C\uB4DC \uC911...`, async () => {
@@ -3499,7 +3834,7 @@ async function fetchCommand(cwd, recipeKey, options) {
3499
3834
  if (result.written.length > 0) {
3500
3835
  success(`${result.written.length}\uAC1C \uD30C\uC77C \uC800\uC7A5`);
3501
3836
  for (const f of result.written) {
3502
- console.log(chalk4.gray(` ${path5.relative(cwd, f)}`));
3837
+ console.log(chalk4.gray(` ${path6.relative(cwd, f)}`));
3503
3838
  }
3504
3839
  }
3505
3840
  if (result.skipped.length > 0) {
@@ -3535,10 +3870,10 @@ async function fetchCommand(cwd, recipeKey, options) {
3535
3870
  }
3536
3871
  async function appendEnvKeys(cwd, recipe) {
3537
3872
  if (recipe.envKeys.length === 0) return;
3538
- const envPath = path5.join(cwd, ".env.local");
3873
+ const envPath = path6.join(cwd, ".env.local");
3539
3874
  let content = "";
3540
- if (await fs11.pathExists(envPath)) {
3541
- content = await fs11.readFile(envPath, "utf8");
3875
+ if (await fs12.pathExists(envPath)) {
3876
+ content = await fs12.readFile(envPath, "utf8");
3542
3877
  }
3543
3878
  const missingKeys = recipe.envKeys.filter((ek) => !content.includes(`${ek.key}=`));
3544
3879
  if (missingKeys.length === 0) return;
@@ -3551,13 +3886,13 @@ async function appendEnvKeys(cwd, recipe) {
3551
3886
  if (ek.hint) lines.push(`# ${ek.hint}`);
3552
3887
  lines.push(`${ek.key}=${ek.default ?? ""}`);
3553
3888
  }
3554
- await fs11.ensureFile(envPath);
3555
- await fs11.appendFile(envPath, lines.join("\n") + "\n");
3889
+ await fs12.ensureFile(envPath);
3890
+ await fs12.appendFile(envPath, lines.join("\n") + "\n");
3556
3891
  }
3557
3892
  async function upsertRecipesSkill(cwd, installedKeys) {
3558
- const skillDir = path5.join(cwd, ".claude", "skills", "recipes");
3559
- await fs11.ensureDir(skillDir);
3560
- const skillPath = path5.join(skillDir, "SKILL.md");
3893
+ const skillDir = path6.join(cwd, ".claude", "skills", "recipes");
3894
+ await fs12.ensureDir(skillDir);
3895
+ const skillPath = path6.join(skillDir, "SKILL.md");
3561
3896
  const recipes = installedKeys.map((k) => RECIPES[k]);
3562
3897
  const triggers = recipes.map((r) => r.skillDescription ?? `${r.label} \uAD00\uB828 \uAE30\uB2A5 \uAD6C\uD604 \uC2DC ${r.target}/ \uCC38\uC870`).join("\n- ");
3563
3898
  const body = [
@@ -3596,10 +3931,10 @@ async function upsertRecipesSkill(cwd, installedKeys) {
3596
3931
  "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",
3597
3932
  ""
3598
3933
  ].join("\n");
3599
- await fs11.writeFile(skillPath, body);
3934
+ await fs12.writeFile(skillPath, body);
3600
3935
  }
3601
3936
  async function upsertClaudeMdBlock(cwd, installedKeys) {
3602
- const claudeMdPath = path5.join(cwd, "CLAUDE.md");
3937
+ const claudeMdPath = path6.join(cwd, "CLAUDE.md");
3603
3938
  const BLOCK_START = "<!-- pai:recipes:start -->";
3604
3939
  const BLOCK_END = "<!-- pai:recipes:end -->";
3605
3940
  const lines = [];
@@ -3619,8 +3954,8 @@ async function upsertClaudeMdBlock(cwd, installedKeys) {
3619
3954
  lines.push(BLOCK_END);
3620
3955
  const block = lines.join("\n");
3621
3956
  let content = "";
3622
- if (await fs11.pathExists(claudeMdPath)) {
3623
- content = await fs11.readFile(claudeMdPath, "utf8");
3957
+ if (await fs12.pathExists(claudeMdPath)) {
3958
+ content = await fs12.readFile(claudeMdPath, "utf8");
3624
3959
  }
3625
3960
  const startIdx = content.indexOf(BLOCK_START);
3626
3961
  const endIdx = content.indexOf(BLOCK_END);
@@ -3633,8 +3968,8 @@ async function upsertClaudeMdBlock(cwd, installedKeys) {
3633
3968
  if (content.length > 0) content += "\n";
3634
3969
  content += block + "\n";
3635
3970
  }
3636
- await fs11.ensureFile(claudeMdPath);
3637
- await fs11.writeFile(claudeMdPath, content);
3971
+ await fs12.ensureFile(claudeMdPath);
3972
+ await fs12.writeFile(claudeMdPath, content);
3638
3973
  }
3639
3974
  var init_fetch_cmd = __esm({
3640
3975
  "src/cli/commands/fetch.cmd.ts"() {
@@ -3658,6 +3993,7 @@ var init_environment = __esm({
3658
3993
  init_installer();
3659
3994
  init_registry();
3660
3995
  init_claude_commands();
3996
+ init_detector();
3661
3997
  init_config();
3662
3998
  init_progress();
3663
3999
  init_ui();
@@ -3715,6 +4051,7 @@ var init_environment = __esm({
3715
4051
  PAI_PROJECT_NAME: interview.projectName,
3716
4052
  PAI_MODE: interview.mode
3717
4053
  },
4054
+ analysis,
3718
4055
  mcp: interview.mcp
3719
4056
  };
3720
4057
  if (interview.authMethods.includes("custom")) {
@@ -3779,6 +4116,7 @@ var init_environment = __esm({
3779
4116
  await withSpinner("\uC124\uC815 \uC800\uC7A5 \uC911...", async () => {
3780
4117
  const config = createDefaultConfig(interview.projectName, interview.mode);
3781
4118
  config.plugins = pluginKeys;
4119
+ config.edges = deriveEdges(pluginKeys);
3782
4120
  if (interview.mcp) config.mcp = interview.mcp;
3783
4121
  await saveConfig(input.cwd, config);
3784
4122
  await sleep(300);
@@ -3808,141 +4146,6 @@ var init_environment = __esm({
3808
4146
  }
3809
4147
  });
3810
4148
 
3811
- // src/core/detector.ts
3812
- var detector_exports = {};
3813
- __export(detector_exports, {
3814
- PLUGIN_META: () => PLUGIN_META,
3815
- PLUGIN_SIGNATURES: () => PLUGIN_SIGNATURES,
3816
- getBasePluginsForMode: () => getBasePluginsForMode,
3817
- getPluginsForMode: () => getPluginsForMode,
3818
- nextMode: () => nextMode,
3819
- pluginsAddedByPromotion: () => pluginsAddedByPromotion,
3820
- scanProjectState: () => scanProjectState
3821
- });
3822
- import path6 from "path";
3823
- import fs12 from "fs-extra";
3824
- async function scanProjectState(cwd) {
3825
- const result = {
3826
- isNewProject: true,
3827
- hasPaiConfig: false,
3828
- projectMode: null,
3829
- installedPlugins: [],
3830
- missingPlugins: [],
3831
- details: {}
3832
- };
3833
- const paiConfigPath = path6.join(cwd, ".pai", "config.json");
3834
- if (await fs12.pathExists(paiConfigPath)) {
3835
- result.hasPaiConfig = true;
3836
- try {
3837
- const config = await fs12.readJson(paiConfigPath);
3838
- result.projectMode = config.mode != null ? normalizeMode(config.mode) : null;
3839
- } catch {
3840
- }
3841
- }
3842
- for (const [key, signatures] of Object.entries(PLUGIN_SIGNATURES)) {
3843
- const installed = await Promise.any(
3844
- signatures.map(async (sig) => {
3845
- if (await fs12.pathExists(path6.join(cwd, sig))) return true;
3846
- throw new Error("not found");
3847
- })
3848
- ).catch(() => false);
3849
- result.details[key] = { installed, signatures };
3850
- if (installed) {
3851
- result.installedPlugins.push(key);
3852
- } else {
3853
- result.missingPlugins.push(key);
3854
- }
3855
- }
3856
- const hasAnyContent = result.installedPlugins.length > 0 || await fs12.pathExists(path6.join(cwd, "package.json")) || await fs12.pathExists(path6.join(cwd, "src")) || await fs12.pathExists(path6.join(cwd, "README.md"));
3857
- result.isNewProject = !hasAnyContent;
3858
- return result;
3859
- }
3860
- function getPluginsForMode(mode) {
3861
- return Object.entries(PLUGIN_META).filter(([, meta]) => meta.modes.includes(mode)).map(([key, meta]) => ({ key, ...meta }));
3862
- }
3863
- function getBasePluginsForMode(mode) {
3864
- return Object.entries(PLUGIN_META).filter(([, meta]) => meta.modes.includes(mode) && meta.required).map(([key]) => key);
3865
- }
3866
- function nextMode(current) {
3867
- if (current === "prototype") return "poc";
3868
- if (current === "poc") return "production";
3869
- return null;
3870
- }
3871
- function pluginsAddedByPromotion(from, to) {
3872
- const fromSet = new Set(getPluginsForMode(from).map((p) => p.key));
3873
- return getPluginsForMode(to).map((p) => p.key).filter((k) => !fromSet.has(k));
3874
- }
3875
- var PLUGIN_SIGNATURES, PLUGIN_META;
3876
- var init_detector = __esm({
3877
- "src/core/detector.ts"() {
3878
- "use strict";
3879
- init_config();
3880
- PLUGIN_SIGNATURES = {
3881
- github: [".git", ".github"],
3882
- vercel: [".vercel", "vercel.json"],
3883
- supabase: ["supabase", ".supabase"],
3884
- openspec: ["openspec.md", "docs/openspec.md", ".pai/openspec.md"],
3885
- omc: [".pai/omc.md", "omc.config.js", "omc.config.json"],
3886
- gstack: [".pai/gstack.json", "gstack.config.js"],
3887
- roboco: [".pai/roboco.json", "roboco.config.js", ".roboco"],
3888
- harness: [".pai/harness.json", "harness.config.js", ".harness"]
3889
- };
3890
- PLUGIN_META = {
3891
- github: {
3892
- label: "GitHub \uB808\uD3EC & \uD3F4\uB354 \uAD6C\uC870",
3893
- description: "\uB808\uD3EC \uCD08\uAE30\uD654, .gitignore, \uAE30\uBCF8 \uBE0C\uB79C\uCE58 \uC124\uC815",
3894
- modes: ["prototype", "poc", "production"],
3895
- required: true
3896
- },
3897
- openspec: {
3898
- label: "OpenSpec (PRD \uC124\uACC4)",
3899
- description: "AI \uAE30\uBC18 PRD \uC0DD\uC131 \uBC0F \uC2A4\uD399 \uBB38\uC11C\uD654",
3900
- modes: ["prototype", "poc", "production"],
3901
- required: true
3902
- },
3903
- roboco: {
3904
- label: "roboco (AI \uC9C4\uB2E8)",
3905
- description: "\uC124\uCE58 \uC0C1\uD0DC \uD3C9\uAC00 \uBC0F AI \uC900\uBE44\uB3C4 \uB9AC\uD3EC\uD2B8 \uC0DD\uC131",
3906
- modes: ["prototype", "poc", "production"],
3907
- required: false,
3908
- url: "https://github.com/SoInKyu/roboco-cli"
3909
- },
3910
- omc: {
3911
- label: "OMC (oh-my-claudecode)",
3912
- description: "\uAC1D\uCCB4 \uBAA8\uB378 \uCEE8\uD14D\uC2A4\uD2B8 + Claude Code \uBA40\uD2F0 \uC5D0\uC774\uC804\uD2B8 \uC624\uCF00\uC2A4\uD2B8\uB808\uC774\uC158",
3913
- modes: ["poc", "production"],
3914
- required: false,
3915
- url: "https://github.com/SoInKyu/oh-my-claudecode"
3916
- },
3917
- vercel: {
3918
- label: "Vercel \uBC30\uD3EC \uC5F0\uB3D9",
3919
- description: "\uC790\uB3D9 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uBC0F Preview URL \uC124\uC815",
3920
- modes: ["poc", "production"],
3921
- required: false
3922
- },
3923
- gstack: {
3924
- label: "gstack (QA / \uD488\uC9C8\uAD00\uB9AC)",
3925
- description: "\uD14C\uC2A4\uD2B8 \uC790\uB3D9\uD654 \uBC0F \uD488\uC9C8 \uAE30\uC900 \uC124\uC815",
3926
- modes: ["production"],
3927
- required: false,
3928
- url: "https://github.com/SoInKyu/gstack"
3929
- },
3930
- harness: {
3931
- label: "Harness Engineering (\uAC80\uC99D \uC790\uB3D9\uD654)",
3932
- description: "\uC124\uACC4(OpenSpec)\uC640 \uAD6C\uD604 \uC77C\uCE58 \uC5EC\uBD80 \uC790\uB3D9 \uCCB4\uD06C",
3933
- modes: ["production"],
3934
- required: false
3935
- },
3936
- supabase: {
3937
- label: "Supabase (DB & Auth)",
3938
- description: "\uB370\uC774\uD130\uBCA0\uC774\uC2A4, \uC778\uC99D, API \uD0A4 \uC790\uB3D9 \uC5F0\uB3D9",
3939
- modes: ["production"],
3940
- required: false
3941
- }
3942
- };
3943
- }
3944
- });
3945
-
3946
4149
  // src/stages/evaluation/prompts/analyze.ts
3947
4150
  var analyze_exports = {};
3948
4151
  __export(analyze_exports, {
@@ -4091,8 +4294,8 @@ async function checkCiCd(repoPath) {
4091
4294
  { path: "Jenkinsfile", label: "Jenkins" },
4092
4295
  { path: ".circleci", label: "CircleCI" }
4093
4296
  ];
4094
- for (const { path: path8, label } of ciConfigs) {
4095
- const found = await fs13.pathExists(join7(repoPath, path8));
4297
+ for (const { path: path10, label } of ciConfigs) {
4298
+ const found = await fs13.pathExists(join7(repoPath, path10));
4096
4299
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
4097
4300
  if (found) score += 40;
4098
4301
  }
@@ -4110,8 +4313,8 @@ async function checkHooks(repoPath) {
4110
4313
  { path: "commitlint.config.js", label: "commitlint" },
4111
4314
  { path: ".claude/settings.json", label: "Claude Code settings" }
4112
4315
  ];
4113
- for (const { path: path8, label } of hookConfigs) {
4114
- const found = await fs13.pathExists(join7(repoPath, path8));
4316
+ for (const { path: path10, label } of hookConfigs) {
4317
+ const found = await fs13.pathExists(join7(repoPath, path10));
4115
4318
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
4116
4319
  if (found) score += 20;
4117
4320
  }
@@ -4128,8 +4331,8 @@ async function checkRepoStructure(repoPath) {
4128
4331
  { path: ".env.example", label: "\uD658\uACBD\uBCC0\uC218 \uC608\uC2DC" },
4129
4332
  { path: ".gitignore", label: ".gitignore" }
4130
4333
  ];
4131
- for (const { path: path8, label } of structureChecks) {
4132
- const found = await fs13.pathExists(join7(repoPath, path8));
4334
+ for (const { path: path10, label } of structureChecks) {
4335
+ const found = await fs13.pathExists(join7(repoPath, path10));
4133
4336
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
4134
4337
  if (found) score += 25;
4135
4338
  }
@@ -4145,8 +4348,8 @@ async function checkDocumentation(repoPath) {
4145
4348
  { path: "docs", label: "docs/ \uB514\uB809\uD1A0\uB9AC", points: 25 },
4146
4349
  { path: "docs/openspec.md", label: "OpenSpec PRD", points: 25 }
4147
4350
  ];
4148
- for (const { path: path8, label, points } of docChecks) {
4149
- const found = await fs13.pathExists(join7(repoPath, path8));
4351
+ for (const { path: path10, label, points } of docChecks) {
4352
+ const found = await fs13.pathExists(join7(repoPath, path10));
4150
4353
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
4151
4354
  if (found) score += points;
4152
4355
  }
@@ -4164,8 +4367,8 @@ async function checkHarnessEngineering(repoPath) {
4164
4367
  { path: ".claude/commands", label: ".claude/commands/", points: 10 },
4165
4368
  { path: ".pai/config.json", label: "PAI config", points: 10 }
4166
4369
  ];
4167
- for (const { path: path8, label, points } of harnessChecks) {
4168
- const found = await fs13.pathExists(join7(repoPath, path8));
4370
+ for (const { path: path10, label, points } of harnessChecks) {
4371
+ const found = await fs13.pathExists(join7(repoPath, path10));
4169
4372
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
4170
4373
  if (found) score += points;
4171
4374
  }
@@ -4390,13 +4593,13 @@ function buildMarkdownReport(result) {
4390
4593
  return lines.join("\n") + "\n";
4391
4594
  }
4392
4595
  function buildDetailedReport(result, projectName) {
4393
- const now = (/* @__PURE__ */ new Date()).toLocaleString("ko-KR");
4596
+ const now2 = (/* @__PURE__ */ new Date()).toLocaleString("ko-KR");
4394
4597
  const mustCats = result.categories.filter((c2) => c2.tier === "must");
4395
4598
  const niceCats = result.categories.filter((c2) => c2.tier === "nice");
4396
4599
  const lines = [
4397
4600
  `# ${projectName} \uBC14\uC774\uBE0C \uCF54\uB529 \uC900\uBE44\uB3C4 \uBD84\uC11D \uB9AC\uD3EC\uD2B8`,
4398
4601
  "",
4399
- `> \uC2A4\uCE94 \uC77C\uC2DC: ${now}`,
4602
+ `> \uC2A4\uCE94 \uC77C\uC2DC: ${now2}`,
4400
4603
  `> \uBD84\uC11D \uB300\uC0C1: ${projectName}`,
4401
4604
  "",
4402
4605
  "---",
@@ -4808,40 +5011,657 @@ function getCachedResult(repoPath) {
4808
5011
  function setCachedResult(repoPath, llmOutput) {
4809
5012
  const store = loadCache(repoPath);
4810
5013
  const repoHash = computeRepoHash(repoPath);
4811
- const now = Date.now();
5014
+ const now2 = Date.now();
4812
5015
  for (const [key, entry] of Object.entries(store.entries)) {
4813
- if (now - entry.timestamp > CACHE_TTL_MS) {
5016
+ if (now2 - entry.timestamp > CACHE_TTL_MS) {
4814
5017
  delete store.entries[key];
4815
5018
  }
4816
5019
  }
4817
- store.entries[repoHash] = { repoPath, repoHash, timestamp: now, llmOutput };
4818
- saveCache(repoPath, store);
5020
+ store.entries[repoHash] = { repoPath, repoHash, timestamp: now2, llmOutput };
5021
+ saveCache(repoPath, store);
5022
+ }
5023
+ var CACHE_DIR, CACHE_FILE, CACHE_TTL_MS, FILES_TO_HASH;
5024
+ var init_cache = __esm({
5025
+ "src/stages/evaluation/cache.ts"() {
5026
+ "use strict";
5027
+ CACHE_DIR = ".pai";
5028
+ CACHE_FILE = "cache/evaluation.json";
5029
+ CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
5030
+ FILES_TO_HASH = [
5031
+ "package.json",
5032
+ "pyproject.toml",
5033
+ "go.mod",
5034
+ "Cargo.toml",
5035
+ "tsconfig.json",
5036
+ "jest.config.ts",
5037
+ "vitest.config.ts",
5038
+ ".github/workflows",
5039
+ ".gitlab-ci.yml",
5040
+ ".husky",
5041
+ ".lintstagedrc",
5042
+ "CLAUDE.md",
5043
+ "AGENTS.md",
5044
+ ".cursorrules",
5045
+ "README.md",
5046
+ "CONTRIBUTING.md"
5047
+ ];
5048
+ }
5049
+ });
5050
+
5051
+ // src/core/roboco.ts
5052
+ import os3 from "os";
5053
+ import path8 from "path";
5054
+ import fs16 from "fs-extra";
5055
+ function robocoPath(cwd) {
5056
+ return path8.join(cwd, ROBOCO_PATH);
5057
+ }
5058
+ async function loadRobocoState(cwd) {
5059
+ const p = robocoPath(cwd);
5060
+ if (!await fs16.pathExists(p)) return freshState();
5061
+ try {
5062
+ const raw = await fs16.readJson(p);
5063
+ if (raw?.version !== "2.0") return freshState();
5064
+ return raw;
5065
+ } catch {
5066
+ return freshState();
5067
+ }
5068
+ }
5069
+ async function saveRobocoState(cwd, state) {
5070
+ const p = robocoPath(cwd);
5071
+ await fs16.ensureDir(path8.dirname(p));
5072
+ await fs16.writeJson(p, state, { spaces: 2 });
5073
+ }
5074
+ function freshState() {
5075
+ return {
5076
+ version: "2.0",
5077
+ currentStage: "environment",
5078
+ stageHistory: [],
5079
+ lock: null,
5080
+ gates: {}
5081
+ };
5082
+ }
5083
+ function isProcessAlive(pid) {
5084
+ try {
5085
+ process.kill(pid, 0);
5086
+ return true;
5087
+ } catch {
5088
+ return false;
5089
+ }
5090
+ }
5091
+ function isLockStale(lock, now2 = /* @__PURE__ */ new Date()) {
5092
+ if (!isProcessAlive(lock.pid)) return true;
5093
+ if (new Date(lock.expiresAt).getTime() < now2.getTime()) return true;
5094
+ return false;
5095
+ }
5096
+ async function acquireLock(cwd, stage, command, ttlMs = DEFAULT_TTL_MS) {
5097
+ const state = await loadRobocoState(cwd);
5098
+ if (state.lock) {
5099
+ if (!isLockStale(state.lock)) {
5100
+ throw new RobocoLockError(formatLockError(state.lock), state.lock);
5101
+ }
5102
+ }
5103
+ const now2 = /* @__PURE__ */ new Date();
5104
+ const newLock = {
5105
+ pid: process.pid,
5106
+ stage,
5107
+ command,
5108
+ acquiredAt: now2.toISOString(),
5109
+ expiresAt: new Date(now2.getTime() + ttlMs).toISOString(),
5110
+ host: os3.hostname()
5111
+ };
5112
+ state.lock = newLock;
5113
+ await saveRobocoState(cwd, state);
5114
+ return state;
5115
+ }
5116
+ async function releaseLock(cwd) {
5117
+ const state = await loadRobocoState(cwd);
5118
+ if (state.lock && state.lock.pid === process.pid) {
5119
+ state.lock = null;
5120
+ await saveRobocoState(cwd, state);
5121
+ }
5122
+ }
5123
+ function formatLockError(lock) {
5124
+ const started = new Date(lock.acquiredAt);
5125
+ const mins = Math.round((Date.now() - started.getTime()) / 6e4);
5126
+ return [
5127
+ "\uB2E4\uB978 PAI \uBA85\uB839\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4",
5128
+ ` stage: ${lock.stage}`,
5129
+ ` command: ${lock.command}`,
5130
+ ` \uC2DC\uC791: ${mins}\uBD84 \uC804 (PID ${lock.pid}${lock.host ? ", host " + lock.host : ""})`,
5131
+ " \uB300\uAE30\uD558\uC9C0 \uC54A\uACE0 \uC989\uC2DC \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uC644\uB8CC \uD6C4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694."
5132
+ ].join("\n");
5133
+ }
5134
+ async function markStageStart(cwd, stage, command) {
5135
+ const state = await loadRobocoState(cwd);
5136
+ state.currentStage = stage;
5137
+ state.stageHistory.push({
5138
+ stage,
5139
+ status: "in_progress",
5140
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
5141
+ by: command
5142
+ });
5143
+ await saveRobocoState(cwd, state);
5144
+ }
5145
+ async function markStageEnd(cwd, stage, success2, reason) {
5146
+ const state = await loadRobocoState(cwd);
5147
+ for (let i = state.stageHistory.length - 1; i >= 0; i--) {
5148
+ const entry = state.stageHistory[i];
5149
+ if (entry.stage === stage && entry.status === "in_progress") {
5150
+ entry.status = success2 ? "done" : "failed";
5151
+ entry.endedAt = (/* @__PURE__ */ new Date()).toISOString();
5152
+ if (reason) entry.reason = reason;
5153
+ break;
5154
+ }
5155
+ }
5156
+ await saveRobocoState(cwd, state);
5157
+ }
5158
+ async function saveGateResult(cwd, key, result) {
5159
+ const state = await loadRobocoState(cwd);
5160
+ state.gates[key] = result;
5161
+ await saveRobocoState(cwd, state);
5162
+ }
5163
+ async function withRoboco(cwd, stage, command, options, fn) {
5164
+ await acquireLock(cwd, stage, command, options.ttlMs);
5165
+ try {
5166
+ if (options.gate && !options.force) {
5167
+ const result = await options.gate(cwd);
5168
+ await saveGateResult(cwd, `${stage}.entry`, result);
5169
+ if (!result.passed && !options.skipGates) {
5170
+ await markStageEnd(cwd, stage, false, `\uAC8C\uC774\uD2B8 \uC2E4\uD328: ${result.name}`);
5171
+ throw new GateFailedError(result);
5172
+ }
5173
+ if (!result.passed && options.skipGates) {
5174
+ }
5175
+ }
5176
+ await markStageStart(cwd, stage, command);
5177
+ const value = await fn();
5178
+ await markStageEnd(cwd, stage, true);
5179
+ return value;
5180
+ } catch (err) {
5181
+ if (!(err instanceof GateFailedError)) {
5182
+ const msg = err instanceof Error ? err.message : String(err);
5183
+ await markStageEnd(cwd, stage, false, msg).catch(() => {
5184
+ });
5185
+ }
5186
+ throw err;
5187
+ } finally {
5188
+ await releaseLock(cwd).catch(() => {
5189
+ });
5190
+ }
5191
+ }
5192
+ async function syncHandoff(cwd) {
5193
+ const handoffPath = path8.join(cwd, "handoff.md");
5194
+ if (!await fs16.pathExists(handoffPath)) return;
5195
+ const state = await loadRobocoState(cwd);
5196
+ const block = buildHandoffBlock(state);
5197
+ const content = await fs16.readFile(handoffPath, "utf8");
5198
+ const startIdx = content.indexOf(HANDOFF_START);
5199
+ const endIdx = content.indexOf(HANDOFF_END);
5200
+ let next;
5201
+ if (startIdx >= 0 && endIdx > startIdx) {
5202
+ next = content.slice(0, startIdx) + block + content.slice(endIdx + HANDOFF_END.length);
5203
+ } else {
5204
+ const sep = content.endsWith("\n") ? "\n" : "\n\n";
5205
+ next = content + sep + block + "\n";
5206
+ }
5207
+ await fs16.writeFile(handoffPath, next);
5208
+ }
5209
+ function buildHandoffBlock(state) {
5210
+ const lines = [HANDOFF_START, "## \uD30C\uC774\uD504\uB77C\uC778 \uC9C4\uD589 \uC0C1\uD0DC (roboco \uC790\uB3D9 \uAD00\uB9AC)", ""];
5211
+ const done = state.stageHistory.filter((h) => h.status === "done");
5212
+ const inProgress = state.stageHistory.filter((h) => h.status === "in_progress");
5213
+ const failed = state.stageHistory.filter((h) => h.status === "failed");
5214
+ if (inProgress.length > 0) {
5215
+ lines.push("### \uC9C4\uD589 \uC911");
5216
+ for (const h of inProgress) {
5217
+ lines.push(`- [ ] ${h.stage} (${h.by}, \uC2DC\uC791 ${fmtDate(h.startedAt)})`);
5218
+ }
5219
+ lines.push("");
5220
+ }
5221
+ if (failed.length > 0) {
5222
+ lines.push("### \uC2E4\uD328");
5223
+ for (const h of failed) {
5224
+ lines.push(`- [x] ${h.stage} \u2014 ${h.reason ?? "\uC6D0\uC778 \uBD88\uBA85"} (${fmtDate(h.startedAt)})`);
5225
+ }
5226
+ lines.push("");
5227
+ }
5228
+ if (done.length > 0) {
5229
+ lines.push("### \uC644\uB8CC");
5230
+ for (const h of done) {
5231
+ lines.push(`- [x] ${h.stage} (${h.by}, ${fmtDate(h.endedAt ?? h.startedAt)})`);
5232
+ }
5233
+ lines.push("");
5234
+ }
5235
+ lines.push(`\uD604\uC7AC \uB2E8\uACC4: **${state.currentStage}**`);
5236
+ lines.push(HANDOFF_END);
5237
+ return lines.join("\n");
5238
+ }
5239
+ function fmtDate(iso) {
5240
+ try {
5241
+ const d = new Date(iso);
5242
+ return d.toISOString().slice(0, 16).replace("T", " ");
5243
+ } catch {
5244
+ return iso;
5245
+ }
5246
+ }
5247
+ var ROBOCO_PATH, DEFAULT_TTL_MS, RobocoLockError, GateFailedError, HANDOFF_START, HANDOFF_END;
5248
+ var init_roboco = __esm({
5249
+ "src/core/roboco.ts"() {
5250
+ "use strict";
5251
+ ROBOCO_PATH = path8.join(".pai", "roboco.json");
5252
+ DEFAULT_TTL_MS = 10 * 60 * 1e3;
5253
+ RobocoLockError = class extends Error {
5254
+ constructor(message, holder) {
5255
+ super(message);
5256
+ this.holder = holder;
5257
+ this.name = "RobocoLockError";
5258
+ }
5259
+ holder;
5260
+ };
5261
+ GateFailedError = class extends Error {
5262
+ constructor(result) {
5263
+ super(`\uAC8C\uC774\uD2B8 \uC2E4\uD328 (${result.name}): ${result.violations.length}\uAC74 \uC704\uBC18`);
5264
+ this.result = result;
5265
+ this.name = "GateFailedError";
5266
+ }
5267
+ result;
5268
+ };
5269
+ HANDOFF_START = "<!-- roboco:start -->";
5270
+ HANDOFF_END = "<!-- roboco:end -->";
5271
+ }
5272
+ });
5273
+
5274
+ // src/core/gates.ts
5275
+ import path9 from "path";
5276
+ import fs17 from "fs-extra";
5277
+ function now() {
5278
+ return (/* @__PURE__ */ new Date()).toISOString();
5279
+ }
5280
+ function makeResult(name, violations) {
5281
+ return {
5282
+ name,
5283
+ passed: violations.length === 0,
5284
+ checkedAt: now(),
5285
+ violations
5286
+ };
5287
+ }
5288
+ async function designEntryGate(cwd) {
5289
+ const violations = [];
5290
+ const claudeMd = path9.join(cwd, "CLAUDE.md");
5291
+ if (!await fs17.pathExists(claudeMd)) {
5292
+ violations.push({
5293
+ rule: "claude-md-exists",
5294
+ message: "CLAUDE.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 `pai init`\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
5295
+ location: "CLAUDE.md"
5296
+ });
5297
+ }
5298
+ const configJson = path9.join(cwd, ".pai", "config.json");
5299
+ if (!await fs17.pathExists(configJson)) {
5300
+ violations.push({
5301
+ rule: "pai-config-exists",
5302
+ message: "PAI \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `pai init`\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
5303
+ location: ".pai/config.json"
5304
+ });
5305
+ }
5306
+ return makeResult("design.entry", violations);
5307
+ }
5308
+ async function executionEntryGate(cwd) {
5309
+ const violations = [];
5310
+ const openspec = path9.join(cwd, "docs", "openspec.md");
5311
+ const hasOpenspec = await fs17.pathExists(openspec);
5312
+ if (!hasOpenspec) {
5313
+ violations.push({
5314
+ rule: "openspec-exists",
5315
+ message: "docs/openspec.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
5316
+ location: "docs/openspec.md"
5317
+ });
5318
+ } else {
5319
+ const content = await fs17.readFile(openspec, "utf8");
5320
+ const endpointCount = countEndpoints(content);
5321
+ if (endpointCount < 1) {
5322
+ violations.push({
5323
+ rule: "openspec-endpoints-min",
5324
+ message: `openspec.md\uC5D0 \uC815\uC758\uB41C API \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uAC00 0\uAC1C\uC785\uB2C8\uB2E4. \uCD5C\uC18C 1\uAC1C \uD544\uC694 (\uD604\uC7AC: 0). /pai design \uC2E4\uD589 \uAD8C\uC7A5.`,
5325
+ location: "docs/openspec.md \xA7 5. API \uC5D4\uB4DC\uD3EC\uC778\uD2B8"
5326
+ });
5327
+ }
5328
+ }
5329
+ const omc = path9.join(cwd, ".pai", "omc.md");
5330
+ if (await fs17.pathExists(omc)) {
5331
+ const content = await fs17.readFile(omc, "utf8");
5332
+ const domainCount = countDomainObjects(content);
5333
+ if (domainCount < 1) {
5334
+ violations.push({
5335
+ rule: "omc-domain-min",
5336
+ message: "omc.md\uC5D0 \uB3C4\uBA54\uC778 \uAC1D\uCCB4\uAC00 0\uAC1C\uC785\uB2C8\uB2E4. \uCD5C\uC18C 1\uAC1C \uD544\uC694.",
5337
+ location: ".pai/omc.md \xA7 \uB3C4\uBA54\uC778 \uAC1D\uCCB4"
5338
+ });
5339
+ }
5340
+ }
5341
+ return makeResult("execution.entry", violations);
5342
+ }
5343
+ async function validationEntryGate(cwd) {
5344
+ const violations = [];
5345
+ const srcDir = path9.join(cwd, "src");
5346
+ if (!await fs17.pathExists(srcDir)) {
5347
+ violations.push({
5348
+ rule: "src-dir-exists",
5349
+ message: "src/ \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
5350
+ location: "src/"
5351
+ });
5352
+ } else {
5353
+ const codeFileCount = await countCodeFiles(srcDir);
5354
+ if (codeFileCount === 0) {
5355
+ violations.push({
5356
+ rule: "code-files-min",
5357
+ message: "src/ \uB0B4\uBD80\uC5D0 \uCF54\uB4DC \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uCD5C\uC18C 1\uAC1C \uD544\uC694.",
5358
+ location: "src/"
5359
+ });
5360
+ }
5361
+ }
5362
+ return makeResult("validation.entry", violations);
5363
+ }
5364
+ async function evaluationEntryGate(cwd) {
5365
+ const violations = [];
5366
+ const state = await loadRobocoState(cwd);
5367
+ const recentValidation = [...state.stageHistory].reverse().find((h) => h.stage === "validation");
5368
+ if (!recentValidation) {
5369
+ violations.push({
5370
+ rule: "validation-executed",
5371
+ message: "validation \uB2E8\uACC4\uAC00 \uD55C \uBC88\uB3C4 \uC2E4\uD589\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 `pai test`\uB97C \uC2E4\uD589\uD558\uC138\uC694."
5372
+ });
5373
+ } else if (recentValidation.status !== "done") {
5374
+ violations.push({
5375
+ rule: "validation-passed",
5376
+ message: `\uCD5C\uADFC validation\uC774 ${recentValidation.status} \uC0C1\uD0DC\uC785\uB2C8\uB2E4. \uD14C\uC2A4\uD2B8\uB97C \uC131\uACF5\uC2DC\uD0A8 \uB4A4 \uC2DC\uB3C4\uD558\uC138\uC694.`
5377
+ });
5378
+ } else {
5379
+ const endedAt = recentValidation.endedAt ?? recentValidation.startedAt;
5380
+ const age = Date.now() - new Date(endedAt).getTime();
5381
+ if (age > EVAL_GATE_WINDOW_MS) {
5382
+ const mins = Math.round(age / 6e4);
5383
+ violations.push({
5384
+ rule: "validation-recent",
5385
+ message: `\uCD5C\uADFC validation \uD1B5\uACFC\uAC00 ${mins}\uBD84 \uC804\uC785\uB2C8\uB2E4 (1\uC2DC\uAC04 \uC774\uB0B4\uC5EC\uC57C \uD568). --force\uB85C \uC6B0\uD68C \uAC00\uB2A5.`
5386
+ });
5387
+ }
5388
+ }
5389
+ return makeResult("evaluation.entry", violations);
5390
+ }
5391
+ function countEndpoints(openspecContent) {
5392
+ const lines = openspecContent.split("\n");
5393
+ let inTable = false;
5394
+ let count = 0;
5395
+ for (const rawLine of lines) {
5396
+ const line = rawLine.trim();
5397
+ if (/^##\s*5\.|^##\s*API|API 엔드포인트/i.test(line)) {
5398
+ inTable = true;
5399
+ continue;
5400
+ }
5401
+ if (!inTable) continue;
5402
+ if (/^##\s/.test(line) && !/5\.|API/i.test(line)) {
5403
+ inTable = false;
5404
+ continue;
5405
+ }
5406
+ if (/^\|\s*Method/i.test(line)) continue;
5407
+ if (/^\|\s*[-:]+\s*\|/.test(line)) continue;
5408
+ const m = line.match(/^\|\s*(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*\|\s*(\/\S+)\s*\|/i);
5409
+ if (m) count++;
5410
+ }
5411
+ return count;
5412
+ }
5413
+ function countDomainObjects(omcContent) {
5414
+ const lines = omcContent.split("\n");
5415
+ let count = 0;
5416
+ let inDomainSection = false;
5417
+ for (const rawLine of lines) {
5418
+ const line = rawLine.trim();
5419
+ if (/^##\s*도메인 객체|^##\s*Domain Objects/i.test(line)) {
5420
+ inDomainSection = true;
5421
+ continue;
5422
+ }
5423
+ if (!inDomainSection) continue;
5424
+ if (/^##\s/.test(line) && !/도메인|Domain/i.test(line)) {
5425
+ inDomainSection = false;
5426
+ continue;
5427
+ }
5428
+ if (/^###\s+\w/.test(line)) count++;
5429
+ }
5430
+ return count;
5431
+ }
5432
+ async function countCodeFiles(srcDir) {
5433
+ const exts = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".kt"]);
5434
+ let count = 0;
5435
+ async function walk(dir) {
5436
+ let entries;
5437
+ try {
5438
+ entries = await fs17.readdir(dir);
5439
+ } catch {
5440
+ return;
5441
+ }
5442
+ for (const name of entries) {
5443
+ if (name.startsWith(".") || name === "node_modules") continue;
5444
+ const full = path9.join(dir, name);
5445
+ const stat = await fs17.stat(full).catch(() => null);
5446
+ if (!stat) continue;
5447
+ if (stat.isDirectory()) {
5448
+ await walk(full);
5449
+ } else if (exts.has(path9.extname(name))) {
5450
+ count++;
5451
+ }
5452
+ }
5453
+ }
5454
+ await walk(srcDir);
5455
+ return count;
5456
+ }
5457
+ var EVAL_GATE_WINDOW_MS, STAGE_GATES;
5458
+ var init_gates = __esm({
5459
+ "src/core/gates.ts"() {
5460
+ "use strict";
5461
+ init_roboco();
5462
+ EVAL_GATE_WINDOW_MS = 60 * 60 * 1e3;
5463
+ STAGE_GATES = {
5464
+ environment: async () => makeResult("environment.entry", []),
5465
+ // 항상 통과
5466
+ design: designEntryGate,
5467
+ execution: executionEntryGate,
5468
+ validation: validationEntryGate,
5469
+ evaluation: evaluationEntryGate
5470
+ };
5471
+ }
5472
+ });
5473
+
5474
+ // src/stages/validation/runner.ts
5475
+ import { join as join10 } from "path";
5476
+ import fs18 from "fs-extra";
5477
+ async function runTests(cwd) {
5478
+ const start = Date.now();
5479
+ const gstackPath = join10(cwd, ".pai", "gstack.json");
5480
+ let runner = "npm test";
5481
+ if (await fs18.pathExists(gstackPath)) {
5482
+ try {
5483
+ const config = await fs18.readJson(gstackPath);
5484
+ if (config.testRunner === "vitest") runner = "npx vitest run";
5485
+ else if (config.testRunner === "jest") runner = "npx jest";
5486
+ else if (config.testRunner === "mocha") runner = "npx mocha";
5487
+ } catch {
5488
+ }
5489
+ }
5490
+ const pkgPath = join10(cwd, "package.json");
5491
+ if (await fs18.pathExists(pkgPath)) {
5492
+ try {
5493
+ const pkg5 = await fs18.readJson(pkgPath);
5494
+ if (!pkg5.scripts?.test || pkg5.scripts.test.includes("no test specified")) {
5495
+ return {
5496
+ runner,
5497
+ passed: false,
5498
+ output: "\uD14C\uC2A4\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC815\uC758\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. package.json\uC758 scripts.test\uB97C \uC124\uC815\uD558\uC138\uC694.",
5499
+ duration: Date.now() - start
5500
+ };
5501
+ }
5502
+ } catch {
5503
+ }
5504
+ }
5505
+ try {
5506
+ const { execa } = await import("execa");
5507
+ const { stdout, stderr } = await execa("npm", ["test"], {
5508
+ cwd,
5509
+ timeout: 12e4,
5510
+ env: { ...process.env, CI: "true" }
5511
+ });
5512
+ return {
5513
+ runner,
5514
+ passed: true,
5515
+ output: stdout || stderr,
5516
+ duration: Date.now() - start
5517
+ };
5518
+ } catch (err) {
5519
+ const output = err instanceof Error ? err.message : String(err);
5520
+ return {
5521
+ runner,
5522
+ passed: false,
5523
+ output,
5524
+ duration: Date.now() - start
5525
+ };
5526
+ }
5527
+ }
5528
+ var init_runner = __esm({
5529
+ "src/stages/validation/runner.ts"() {
5530
+ "use strict";
5531
+ }
5532
+ });
5533
+
5534
+ // src/stages/validation/harness.ts
5535
+ import { join as join11 } from "path";
5536
+ import fs19 from "fs-extra";
5537
+ async function runHarnessCheck(cwd) {
5538
+ const harnessPath = join11(cwd, ".pai", "harness.json");
5539
+ if (!await fs19.pathExists(harnessPath)) {
5540
+ return { enabled: false, specFile: null, rules: [], checks: [] };
5541
+ }
5542
+ let config;
5543
+ try {
5544
+ config = await fs19.readJson(harnessPath);
5545
+ } catch {
5546
+ return { enabled: false, specFile: null, rules: [], checks: [] };
5547
+ }
5548
+ const specFile = config.specFile ?? "docs/openspec.md";
5549
+ const rules = config.rules ?? [];
5550
+ const checks = [];
5551
+ if (rules.includes("spec-implementation-match")) {
5552
+ const specExists = await fs19.pathExists(join11(cwd, specFile));
5553
+ const srcExists = await fs19.pathExists(join11(cwd, "src"));
5554
+ checks.push({
5555
+ rule: "spec-implementation-match",
5556
+ passed: specExists && srcExists,
5557
+ detail: specExists && srcExists ? "\uC124\uACC4 \uBB38\uC11C\uC640 \uC18C\uC2A4 \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC" : `${!specExists ? specFile + " \uC5C6\uC74C" : ""} ${!srcExists ? "src/ \uC5C6\uC74C" : ""}`.trim()
5558
+ });
5559
+ }
5560
+ if (rules.includes("api-contract-test")) {
5561
+ const testDir = await fs19.pathExists(join11(cwd, "tests"));
5562
+ const testDir2 = await fs19.pathExists(join11(cwd, "test"));
5563
+ checks.push({
5564
+ rule: "api-contract-test",
5565
+ passed: testDir || testDir2,
5566
+ detail: testDir || testDir2 ? "\uD14C\uC2A4\uD2B8 \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC" : "\uD14C\uC2A4\uD2B8 \uB514\uB809\uD1A0\uB9AC(tests/ \uB610\uB294 test/) \uC5C6\uC74C"
5567
+ });
5568
+ }
5569
+ return { enabled: true, specFile, rules, checks };
5570
+ }
5571
+ var init_harness = __esm({
5572
+ "src/stages/validation/harness.ts"() {
5573
+ "use strict";
5574
+ }
5575
+ });
5576
+
5577
+ // src/cli/commands/validate.cmd.ts
5578
+ var validate_cmd_exports = {};
5579
+ __export(validate_cmd_exports, {
5580
+ handleRobocoCliError: () => handleRobocoError,
5581
+ validateCommand: () => validateCommand
5582
+ });
5583
+ async function validateCommand(cwd, options = {}) {
5584
+ try {
5585
+ await withRoboco(
5586
+ cwd,
5587
+ "validation",
5588
+ "pai test",
5589
+ { skipGates: options.skipGates, force: options.force, gate: STAGE_GATES.validation },
5590
+ async () => {
5591
+ section("\uD14C\uC2A4\uD2B8 \uC2E4\uD589");
5592
+ const testResult = await runTests(cwd);
5593
+ if (testResult.passed) {
5594
+ success(`\uD14C\uC2A4\uD2B8 \uD1B5\uACFC (${testResult.runner}, ${testResult.duration}ms)`);
5595
+ } else {
5596
+ error("\uD14C\uC2A4\uD2B8 \uC2E4\uD328");
5597
+ info(testResult.output.slice(0, 300));
5598
+ }
5599
+ section("\uD558\uB124\uC2A4 \uAC80\uC99D");
5600
+ const harness = await runHarnessCheck(cwd);
5601
+ if (!harness.enabled) {
5602
+ info("Harness \uC124\uC815 \uC5C6\uC74C \u2014 \uAC74\uB108\uB700");
5603
+ info("\uC124\uC815 \uCD94\uAC00: `pai add` \uC5D0\uC11C Harness Engineering \uC120\uD0DD");
5604
+ } else {
5605
+ for (const check of harness.checks) {
5606
+ if (check.passed) {
5607
+ success(`${check.rule}: ${check.detail}`);
5608
+ } else {
5609
+ warn(`${check.rule}: ${check.detail}`);
5610
+ }
5611
+ }
5612
+ }
5613
+ const allPassed = testResult.passed && harness.checks.every((c2) => c2.passed);
5614
+ console.log("");
5615
+ if (allPassed) {
5616
+ success("\uAC80\uC99D \uD1B5\uACFC!");
5617
+ } else if (!testResult.passed) {
5618
+ error("\uAC80\uC99D \uC2E4\uD328 \u2014 \uD14C\uC2A4\uD2B8\uB97C \uC218\uC815\uD558\uC138\uC694.");
5619
+ throw new Error("tests-failed");
5620
+ } else {
5621
+ warn("\uBD80\uBD84 \uD1B5\uACFC \u2014 \uD558\uB124\uC2A4 \uAC80\uC99D \uD56D\uBAA9\uC744 \uD655\uC778\uD558\uC138\uC694.");
5622
+ }
5623
+ }
5624
+ );
5625
+ await syncHandoff(cwd).catch(() => {
5626
+ });
5627
+ } catch (err) {
5628
+ handleRobocoError(err);
5629
+ }
5630
+ }
5631
+ function handleRobocoError(err) {
5632
+ if (err instanceof RobocoLockError) {
5633
+ error(err.message);
5634
+ process.exitCode = 1;
5635
+ return;
5636
+ }
5637
+ if (err instanceof GateFailedError) {
5638
+ error(`\uB2E8\uACC4 \uC9C4\uC785 \uC2E4\uD328 (${err.result.name})`);
5639
+ for (const v of err.result.violations) {
5640
+ warn(` \xB7 ${v.message}${v.location ? ` [${v.location}]` : ""}`);
5641
+ }
5642
+ hint("\uC6B0\uD68C \uC635\uC158: --skip-gates (\uACBD\uACE0\uB9CC) / --force (\uAC8C\uC774\uD2B8 \uBB34\uC2DC)");
5643
+ process.exitCode = 1;
5644
+ return;
5645
+ }
5646
+ if (err instanceof Error) {
5647
+ if (err.message === "tests-failed") {
5648
+ process.exitCode = 1;
5649
+ return;
5650
+ }
5651
+ error(err.message);
5652
+ process.exitCode = 1;
5653
+ return;
5654
+ }
5655
+ throw err;
4819
5656
  }
4820
- var CACHE_DIR, CACHE_FILE, CACHE_TTL_MS, FILES_TO_HASH;
4821
- var init_cache = __esm({
4822
- "src/stages/evaluation/cache.ts"() {
5657
+ var init_validate_cmd = __esm({
5658
+ "src/cli/commands/validate.cmd.ts"() {
4823
5659
  "use strict";
4824
- CACHE_DIR = ".pai";
4825
- CACHE_FILE = "cache/evaluation.json";
4826
- CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4827
- FILES_TO_HASH = [
4828
- "package.json",
4829
- "pyproject.toml",
4830
- "go.mod",
4831
- "Cargo.toml",
4832
- "tsconfig.json",
4833
- "jest.config.ts",
4834
- "vitest.config.ts",
4835
- ".github/workflows",
4836
- ".gitlab-ci.yml",
4837
- ".husky",
4838
- ".lintstagedrc",
4839
- "CLAUDE.md",
4840
- "AGENTS.md",
4841
- ".cursorrules",
4842
- "README.md",
4843
- "CONTRIBUTING.md"
4844
- ];
5660
+ init_ui();
5661
+ init_runner();
5662
+ init_harness();
5663
+ init_roboco();
5664
+ init_gates();
4845
5665
  }
4846
5666
  });
4847
5667
 
@@ -4850,43 +5670,57 @@ var evaluate_cmd_exports = {};
4850
5670
  __export(evaluate_cmd_exports, {
4851
5671
  evaluateCommand: () => evaluateCommand
4852
5672
  });
4853
- import { join as join10, basename } from "path";
4854
- import fs16 from "fs-extra";
5673
+ import { join as join12, basename } from "path";
5674
+ import fs20 from "fs-extra";
4855
5675
  async function evaluateCommand(cwd, options) {
4856
5676
  const useCache = options.cache !== false;
4857
- let llmOutput = useCache ? getCachedResult(cwd) : null;
4858
- if (llmOutput) {
4859
- info("\uCE90\uC2DC\uB41C \uACB0\uACFC \uC0AC\uC6A9 (24\uC2DC\uAC04 \uC774\uB0B4)");
4860
- } else {
4861
- llmOutput = await analyzeRepository(cwd);
4862
- if (useCache) {
4863
- setCachedResult(cwd, llmOutput);
5677
+ try {
5678
+ await withRoboco(
5679
+ cwd,
5680
+ "evaluation",
5681
+ "pai grade",
5682
+ { skipGates: options.skipGates, force: options.force, gate: STAGE_GATES.evaluation },
5683
+ async () => {
5684
+ let llmOutput = useCache ? getCachedResult(cwd) : null;
5685
+ if (llmOutput) {
5686
+ info("\uCE90\uC2DC\uB41C \uACB0\uACFC \uC0AC\uC6A9 (24\uC2DC\uAC04 \uC774\uB0B4)");
5687
+ } else {
5688
+ llmOutput = await analyzeRepository(cwd);
5689
+ if (useCache) setCachedResult(cwd, llmOutput);
5690
+ }
5691
+ const result = computeResult(llmOutput);
5692
+ printReport(result);
5693
+ if (options.verbose) printVerboseFindings(result);
5694
+ const config = await loadConfig(cwd);
5695
+ const projectName = config?.projectName ?? basename(cwd);
5696
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5697
+ const reportDir = join12(cwd, "docs", "p-reports");
5698
+ const reportPath = join12(reportDir, `${today}.md`);
5699
+ await fs20.ensureDir(reportDir);
5700
+ const detailedReport = buildDetailedReport(result, projectName);
5701
+ await fs20.writeFile(reportPath, detailedReport, "utf8");
5702
+ console.log("");
5703
+ success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
5704
+ console.log("");
5705
+ console.log(detailedReport);
5706
+ if (options.output) {
5707
+ await fs20.writeFile(options.output, detailedReport, "utf8");
5708
+ success(`\uCD94\uAC00 \uC800\uC7A5: ${options.output}`);
5709
+ }
5710
+ if (options.failUnder && result.totalScore < options.failUnder) {
5711
+ error(`\uC810\uC218 ${result.totalScore}\uC774 \uCD5C\uC18C \uAE30\uC900 ${options.failUnder}\uC5D0 \uBBF8\uB2EC\uD569\uB2C8\uB2E4.`);
5712
+ throw new Error("score-below-threshold");
5713
+ }
5714
+ }
5715
+ );
5716
+ await syncHandoff(cwd).catch(() => {
5717
+ });
5718
+ } catch (err) {
5719
+ if (err instanceof Error && err.message === "score-below-threshold") {
5720
+ process.exitCode = 1;
5721
+ return;
4864
5722
  }
4865
- }
4866
- const result = computeResult(llmOutput);
4867
- printReport(result);
4868
- if (options.verbose) {
4869
- printVerboseFindings(result);
4870
- }
4871
- const config = await loadConfig(cwd);
4872
- const projectName = config?.projectName ?? basename(cwd);
4873
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4874
- const reportDir = join10(cwd, "docs", "p-reports");
4875
- const reportPath = join10(reportDir, `${today}.md`);
4876
- await fs16.ensureDir(reportDir);
4877
- const detailedReport = buildDetailedReport(result, projectName);
4878
- await fs16.writeFile(reportPath, detailedReport, "utf8");
4879
- console.log("");
4880
- success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
4881
- console.log("");
4882
- console.log(detailedReport);
4883
- if (options.output) {
4884
- await fs16.writeFile(options.output, detailedReport, "utf8");
4885
- success(`\uCD94\uAC00 \uC800\uC7A5: ${options.output}`);
4886
- }
4887
- if (options.failUnder && result.totalScore < options.failUnder) {
4888
- error(`\uC810\uC218 ${result.totalScore}\uC774 \uCD5C\uC18C \uAE30\uC900 ${options.failUnder}\uC5D0 \uBBF8\uB2EC\uD569\uB2C8\uB2E4.`);
4889
- process.exitCode = 1;
5723
+ handleRobocoError(err);
4890
5724
  }
4891
5725
  }
4892
5726
  var init_evaluate_cmd = __esm({
@@ -4898,6 +5732,9 @@ var init_evaluate_cmd = __esm({
4898
5732
  init_cache();
4899
5733
  init_reporter();
4900
5734
  init_config();
5735
+ init_roboco();
5736
+ init_gates();
5737
+ init_validate_cmd();
4901
5738
  }
4902
5739
  });
4903
5740
 
@@ -4908,7 +5745,22 @@ __export(env_cmd_exports, {
4908
5745
  envSetupCommand: () => envSetupCommand,
4909
5746
  envStatusCommand: () => envStatusCommand
4910
5747
  });
4911
- async function envSetupCommand(cwd) {
5748
+ async function envSetupCommand(cwd, options = {}) {
5749
+ try {
5750
+ await withRoboco(
5751
+ cwd,
5752
+ "environment",
5753
+ "pai add",
5754
+ { skipGates: options.skipGates, force: options.force },
5755
+ async () => envSetupCommandInner(cwd)
5756
+ );
5757
+ await syncHandoff(cwd).catch(() => {
5758
+ });
5759
+ } catch (err) {
5760
+ handleRobocoError(err);
5761
+ }
5762
+ }
5763
+ async function envSetupCommandInner(cwd) {
4912
5764
  const { default: inquirer } = await import("inquirer");
4913
5765
  const { basename: basename5 } = await import("path");
4914
5766
  section("\uD50C\uB7EC\uADF8\uC778 \uCD94\uAC00 \xB7 \uBAA8\uB4DC \uC2B9\uACA9");
@@ -5034,6 +5886,54 @@ async function envStatusCommand(cwd) {
5034
5886
  hint(`\uC2B9\uACA9 \uACBD\uB85C: ${state.projectMode} \u2192 ${next} (pai add \uC5D0\uC11C \uC2B9\uACA9 \uAC00\uB2A5)`);
5035
5887
  }
5036
5888
  }
5889
+ const robo = await loadRobocoState(cwd);
5890
+ console.log("");
5891
+ console.log(colors.accent(" \uD30C\uC774\uD504\uB77C\uC778 \uC9C4\uD589 (roboco)"));
5892
+ 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"));
5893
+ const stages = ["environment", "design", "execution", "validation", "evaluation"];
5894
+ for (const s of stages) {
5895
+ const latest = [...robo.stageHistory].reverse().find((h) => h.stage === s);
5896
+ const gate = robo.gates[`${s}.entry`];
5897
+ let mark = "\u25CB";
5898
+ let label = "\uB300\uAE30";
5899
+ if (latest?.status === "done") {
5900
+ mark = "\u2713";
5901
+ label = "\uC644\uB8CC";
5902
+ } else if (latest?.status === "in_progress") {
5903
+ mark = "\u25B8";
5904
+ label = "\uC9C4\uD589 \uC911";
5905
+ } else if (latest?.status === "failed") {
5906
+ mark = "\u2717";
5907
+ label = `\uC2E4\uD328 (${latest.reason ?? "\uC6D0\uC778 \uBD88\uBA85"})`;
5908
+ } else if (gate && !gate.passed) {
5909
+ mark = "\u25CB";
5910
+ label = `\uB300\uAE30 (\uAC8C\uC774\uD2B8 ${gate.violations.length}\uAC74 \uBBF8\uD1B5\uACFC)`;
5911
+ }
5912
+ console.log(` ${mark} ${s.padEnd(12)} ${colors.dim(label)}`);
5913
+ }
5914
+ const failedGates = Object.values(robo.gates).filter((g) => !g.passed);
5915
+ if (failedGates.length > 0) {
5916
+ console.log("");
5917
+ console.log(colors.accent(" \uBBF8\uD1B5\uACFC \uAC8C\uC774\uD2B8"));
5918
+ for (const g of failedGates) {
5919
+ console.log(` \u2717 ${g.name}`);
5920
+ for (const v of g.violations) {
5921
+ console.log(colors.dim(` \xB7 ${v.message}`));
5922
+ }
5923
+ }
5924
+ }
5925
+ console.log("");
5926
+ if (robo.lock) {
5927
+ const stale = isLockStale(robo.lock);
5928
+ const mins = Math.round((Date.now() - new Date(robo.lock.acquiredAt).getTime()) / 6e4);
5929
+ if (stale) {
5930
+ warn(`\uB77D stale (${robo.lock.command}, PID ${robo.lock.pid}) \u2014 \uB2E4\uC74C \uBA85\uB839 \uC2DC \uC790\uB3D9 \uD574\uC81C\uB428`);
5931
+ } else {
5932
+ warn(`\uC9C4\uD589 \uC911: ${robo.lock.command} (PID ${robo.lock.pid}, ${mins}\uBD84 \uC804)`);
5933
+ }
5934
+ } else {
5935
+ success("\uB77D \uC5C6\uC74C (\uC5B4\uB290 \uBA85\uB839\uC774\uB4E0 \uC2E4\uD589 \uAC00\uB2A5)");
5936
+ }
5037
5937
  }
5038
5938
  var init_env_cmd = __esm({
5039
5939
  "src/cli/commands/env.cmd.ts"() {
@@ -5045,6 +5945,8 @@ var init_env_cmd = __esm({
5045
5945
  init_doctor();
5046
5946
  init_analyzer();
5047
5947
  init_logger();
5948
+ init_roboco();
5949
+ init_validate_cmd();
5048
5950
  }
5049
5951
  });
5050
5952
 
@@ -5053,8 +5955,8 @@ var init_cmd_exports = {};
5053
5955
  __export(init_cmd_exports, {
5054
5956
  initCommand: () => initCommand
5055
5957
  });
5056
- import { join as join11, basename as basename2 } from "path";
5057
- import fs17 from "fs-extra";
5958
+ import { join as join13, basename as basename2 } from "path";
5959
+ import fs21 from "fs-extra";
5058
5960
  async function initCommand(cwd, nameArg) {
5059
5961
  printWelcomeBanner();
5060
5962
  const { isWindows: isWindows2, diagnoseWindowsEnv: diagnoseWindowsEnv2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
@@ -5112,11 +6014,11 @@ async function initCommand(cwd, nameArg) {
5112
6014
  const evalResult = computeResult2(llmOutput);
5113
6015
  printReport2(evalResult);
5114
6016
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5115
- const reportDir = join11(cwd, "docs", "p-reports");
5116
- await fs17.ensureDir(reportDir);
6017
+ const reportDir = join13(cwd, "docs", "p-reports");
6018
+ await fs21.ensureDir(reportDir);
5117
6019
  const legacyName = basename2(cwd);
5118
6020
  const detailedReport = buildDetailedReport3(evalResult, legacyName);
5119
- await fs17.writeFile(join11(reportDir, `${today}.md`), detailedReport, "utf8");
6021
+ await fs21.writeFile(join13(reportDir, `${today}.md`), detailedReport, "utf8");
5120
6022
  console.log("");
5121
6023
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
5122
6024
  } catch {
@@ -5161,8 +6063,8 @@ async function initCommand(cwd, nameArg) {
5161
6063
  }]);
5162
6064
  projectName = answer.name.trim();
5163
6065
  }
5164
- const projectDir = join11(cwd, projectName);
5165
- if (await fs17.pathExists(projectDir)) {
6066
+ const projectDir = join13(cwd, projectName);
6067
+ if (await fs21.pathExists(projectDir)) {
5166
6068
  const existingConfig = await loadConfig(projectDir);
5167
6069
  if (existingConfig) {
5168
6070
  console.log("");
@@ -5175,7 +6077,7 @@ async function initCommand(cwd, nameArg) {
5175
6077
  warn(`${projectName}/ \uD3F4\uB354\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4.`);
5176
6078
  hint("\uC774 \uD3F4\uB354\uC5D0 PAI\uB97C \uC124\uCE58\uD569\uB2C8\uB2E4.");
5177
6079
  } else {
5178
- await fs17.ensureDir(projectDir);
6080
+ await fs21.ensureDir(projectDir);
5179
6081
  success(`${projectName}/ \uD3F4\uB354 \uC0DD\uC131`);
5180
6082
  }
5181
6083
  await setupInDirectory(projectDir, projectName);
@@ -5260,7 +6162,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
5260
6162
  ...extraTools.includes("omc") ? [{ label: "OMC \uB7F0\uD0C0\uC784", path: ".omc" }] : []
5261
6163
  ];
5262
6164
  for (const check of checks) {
5263
- const exists = await fs17.pathExists(join11(projectDir, check.path));
6165
+ const exists = await fs21.pathExists(join13(projectDir, check.path));
5264
6166
  console.log(` ${exists ? colors.success("\u2713") : colors.err("\u2717")} ${check.label.padEnd(16)} ${colors.dim(check.path)}`);
5265
6167
  }
5266
6168
  console.log("");
@@ -5285,10 +6187,10 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
5285
6187
  printReport2(evalResult);
5286
6188
  await sleep2(500);
5287
6189
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5288
- const reportDir = join11(projectDir, "docs", "p-reports");
5289
- await fs17.ensureDir(reportDir);
6190
+ const reportDir = join13(projectDir, "docs", "p-reports");
6191
+ await fs21.ensureDir(reportDir);
5290
6192
  const detailedReport = buildDetailedReport3(evalResult, projectName);
5291
- await fs17.writeFile(join11(reportDir, `${today}.md`), detailedReport, "utf8");
6193
+ await fs21.writeFile(join13(reportDir, `${today}.md`), detailedReport, "utf8");
5292
6194
  await sleep2(500);
5293
6195
  console.log("");
5294
6196
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
@@ -5339,7 +6241,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
5339
6241
  const shellRc = getShellRcPath2();
5340
6242
  let yoloAlreadyAliased = false;
5341
6243
  try {
5342
- const rcContent = await fs17.readFile(shellRc, "utf8");
6244
+ const rcContent = await fs21.readFile(shellRc, "utf8");
5343
6245
  yoloAlreadyAliased = checkYolo(rcContent);
5344
6246
  } catch {
5345
6247
  }
@@ -5359,10 +6261,10 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
5359
6261
  try {
5360
6262
  const { getYoloAliasLine: getYoloAliasLine2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
5361
6263
  const aliasLine = getYoloAliasLine2();
5362
- const rcContent = await fs17.readFile(shellRc, "utf8").catch(() => "");
6264
+ const rcContent = await fs21.readFile(shellRc, "utf8").catch(() => "");
5363
6265
  if (!rcContent.includes("claude-yolo")) {
5364
- await fs17.ensureDir(join11(shellRc, ".."));
5365
- await fs17.appendFile(shellRc, `
6266
+ await fs21.ensureDir(join13(shellRc, ".."));
6267
+ await fs21.appendFile(shellRc, `
5366
6268
  # PAI \u2014 claude-YOLO mode
5367
6269
  ${aliasLine}
5368
6270
  `);
@@ -5555,9 +6457,9 @@ async function installOrchestratorOnly(projectDir, projectName) {
5555
6457
  const evalResult = computeResult2(llmOutput);
5556
6458
  printReport2(evalResult);
5557
6459
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5558
- const reportDir = join11(projectDir, "docs", "p-reports");
5559
- await fs17.ensureDir(reportDir);
5560
- await fs17.writeFile(join11(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
6460
+ const reportDir = join13(projectDir, "docs", "p-reports");
6461
+ await fs21.ensureDir(reportDir);
6462
+ await fs21.writeFile(join13(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
5561
6463
  console.log("");
5562
6464
  hint(`\uC0C1\uC138 \uB9AC\uD3EC\uD2B8: docs/p-reports/${today}.md`);
5563
6465
  } catch {
@@ -5607,7 +6509,7 @@ async function detectLegacyProject(cwd) {
5607
6509
  ".gitignore"
5608
6510
  ];
5609
6511
  for (const signal of signals) {
5610
- if (await fs17.pathExists(join11(cwd, signal))) return true;
6512
+ if (await fs21.pathExists(join13(cwd, signal))) return true;
5611
6513
  }
5612
6514
  return false;
5613
6515
  }
@@ -5698,17 +6600,17 @@ var init_help_cmd = __esm({
5698
6600
  });
5699
6601
 
5700
6602
  // src/stages/design/openspec.ts
5701
- import { join as join12 } from "path";
5702
- import fs18 from "fs-extra";
6603
+ import { join as join14 } from "path";
6604
+ import fs22 from "fs-extra";
5703
6605
  async function initOpenSpec(cwd, projectName) {
5704
- const docsDir = join12(cwd, "docs");
5705
- await fs18.ensureDir(docsDir);
5706
- const openspecPath = join12(docsDir, "openspec.md");
5707
- if (await fs18.pathExists(openspecPath)) {
6606
+ const docsDir = join14(cwd, "docs");
6607
+ await fs22.ensureDir(docsDir);
6608
+ const openspecPath = join14(docsDir, "openspec.md");
6609
+ if (await fs22.pathExists(openspecPath)) {
5708
6610
  info("docs/openspec.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
5709
6611
  return;
5710
6612
  }
5711
- await fs18.writeFile(openspecPath, [
6613
+ await fs22.writeFile(openspecPath, [
5712
6614
  `# OpenSpec \u2014 ${projectName}`,
5713
6615
  "",
5714
6616
  "## 1. \uBAA9\uC801 (Purpose)",
@@ -5736,13 +6638,13 @@ async function initOpenSpec(cwd, projectName) {
5736
6638
  }
5737
6639
  async function validateOpenSpec(cwd) {
5738
6640
  const candidates = [
5739
- join12(cwd, "docs", "openspec.md"),
5740
- join12(cwd, "openspec.md"),
5741
- join12(cwd, ".pai", "openspec.md")
6641
+ join14(cwd, "docs", "openspec.md"),
6642
+ join14(cwd, "openspec.md"),
6643
+ join14(cwd, ".pai", "openspec.md")
5742
6644
  ];
5743
6645
  let specPath = null;
5744
6646
  for (const p of candidates) {
5745
- if (await fs18.pathExists(p)) {
6647
+ if (await fs22.pathExists(p)) {
5746
6648
  specPath = p;
5747
6649
  break;
5748
6650
  }
@@ -5756,7 +6658,7 @@ async function validateOpenSpec(cwd) {
5756
6658
  warnings: ["openspec.md \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `pai design init` \uC744 \uC2E4\uD589\uD558\uC138\uC694."]
5757
6659
  };
5758
6660
  }
5759
- const content = await fs18.readFile(specPath, "utf8");
6661
+ const content = await fs22.readFile(specPath, "utf8");
5760
6662
  const missing = [];
5761
6663
  let filled = 0;
5762
6664
  for (const section2 of REQUIRED_SECTIONS) {
@@ -5804,17 +6706,17 @@ var init_openspec = __esm({
5804
6706
  });
5805
6707
 
5806
6708
  // src/stages/design/omc.ts
5807
- import { join as join13 } from "path";
5808
- import fs19 from "fs-extra";
6709
+ import { join as join15 } from "path";
6710
+ import fs23 from "fs-extra";
5809
6711
  async function initOMC(cwd, projectName) {
5810
- const paiDir = join13(cwd, ".pai");
5811
- await fs19.ensureDir(paiDir);
5812
- const omcPath = join13(paiDir, "omc.md");
5813
- if (await fs19.pathExists(omcPath)) {
6712
+ const paiDir = join15(cwd, ".pai");
6713
+ await fs23.ensureDir(paiDir);
6714
+ const omcPath = join15(paiDir, "omc.md");
6715
+ if (await fs23.pathExists(omcPath)) {
5814
6716
  info(".pai/omc.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
5815
6717
  return;
5816
6718
  }
5817
- await fs19.writeFile(omcPath, [
6719
+ await fs23.writeFile(omcPath, [
5818
6720
  `# OMC \u2014 Object Model Context (${projectName})`,
5819
6721
  "",
5820
6722
  "> AI\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uB3C4\uBA54\uC778\uC744 \uC774\uD574\uD558\uAE30 \uC704\uD55C \uD575\uC2EC \uAC1D\uCCB4 \uBAA8\uB378",
@@ -5854,195 +6756,71 @@ __export(design_cmd_exports, {
5854
6756
  designValidateCommand: () => designValidateCommand
5855
6757
  });
5856
6758
  import { basename as basename3 } from "path";
5857
- async function designInitCommand(cwd) {
5858
- section("\uC124\uACC4 \uD15C\uD50C\uB9BF \uC0DD\uC131");
5859
- const config = await loadConfig(cwd);
5860
- const projectName = config?.projectName ?? basename3(cwd);
5861
- await initOpenSpec(cwd, projectName);
5862
- await initOMC(cwd, projectName);
5863
- info("");
5864
- info("\uB2E4\uC74C \uB2E8\uACC4: docs/openspec.md\uB97C \uC5F4\uC5B4 PRD\uB97C \uC791\uC131\uD558\uC138\uC694.");
5865
- }
5866
- async function designValidateCommand(cwd) {
5867
- section("PRD \uC644\uC131\uB3C4 \uAC80\uC99D");
5868
- const result = await validateOpenSpec(cwd);
5869
- info(`\uC139\uC158 \uC644\uC131\uB3C4: ${result.filledSections}/${result.totalSections}`);
5870
- if (result.missing.length > 0) {
5871
- console.log("");
5872
- warn("\uBBF8\uC791\uC131 \uC139\uC158:");
5873
- for (const m of result.missing) {
5874
- warn(` - ${m}`);
5875
- }
5876
- }
5877
- for (const w of result.warnings) {
5878
- warn(w);
5879
- }
5880
- if (result.complete) {
5881
- console.log("");
5882
- success("PRD \uC644\uC131\uB3C4 \uAC80\uC99D \uD1B5\uACFC!");
5883
- } else {
5884
- console.log("");
5885
- info("docs/openspec.md\uB97C \uC5F4\uC5B4 \uBBF8\uC791\uC131 \uC139\uC158\uC744 \uCC44\uC6CC\uC8FC\uC138\uC694.");
5886
- }
5887
- }
5888
- var init_design_cmd = __esm({
5889
- "src/cli/commands/design.cmd.ts"() {
5890
- "use strict";
5891
- init_ui();
5892
- init_openspec();
5893
- init_omc();
5894
- init_config();
5895
- }
5896
- });
5897
-
5898
- // src/stages/validation/runner.ts
5899
- import { join as join14 } from "path";
5900
- import fs20 from "fs-extra";
5901
- async function runTests(cwd) {
5902
- const start = Date.now();
5903
- const gstackPath = join14(cwd, ".pai", "gstack.json");
5904
- let runner = "npm test";
5905
- if (await fs20.pathExists(gstackPath)) {
5906
- try {
5907
- const config = await fs20.readJson(gstackPath);
5908
- if (config.testRunner === "vitest") runner = "npx vitest run";
5909
- else if (config.testRunner === "jest") runner = "npx jest";
5910
- else if (config.testRunner === "mocha") runner = "npx mocha";
5911
- } catch {
5912
- }
5913
- }
5914
- const pkgPath = join14(cwd, "package.json");
5915
- if (await fs20.pathExists(pkgPath)) {
5916
- try {
5917
- const pkg5 = await fs20.readJson(pkgPath);
5918
- if (!pkg5.scripts?.test || pkg5.scripts.test.includes("no test specified")) {
5919
- return {
5920
- runner,
5921
- passed: false,
5922
- output: "\uD14C\uC2A4\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC815\uC758\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. package.json\uC758 scripts.test\uB97C \uC124\uC815\uD558\uC138\uC694.",
5923
- duration: Date.now() - start
5924
- };
5925
- }
5926
- } catch {
5927
- }
5928
- }
6759
+ async function designInitCommand(cwd, options = {}) {
5929
6760
  try {
5930
- const { execa } = await import("execa");
5931
- const { stdout, stderr } = await execa("npm", ["test"], {
6761
+ await withRoboco(
5932
6762
  cwd,
5933
- timeout: 12e4,
5934
- env: { ...process.env, CI: "true" }
6763
+ "design",
6764
+ "pai design init",
6765
+ { skipGates: options.skipGates, force: options.force, gate: STAGE_GATES.design },
6766
+ async () => {
6767
+ section("\uC124\uACC4 \uD15C\uD50C\uB9BF \uC0DD\uC131");
6768
+ const config = await loadConfig(cwd);
6769
+ const projectName = config?.projectName ?? basename3(cwd);
6770
+ await initOpenSpec(cwd, projectName);
6771
+ await initOMC(cwd, projectName);
6772
+ info("");
6773
+ info("\uB2E4\uC74C \uB2E8\uACC4: docs/openspec.md\uB97C \uC5F4\uC5B4 PRD\uB97C \uC791\uC131\uD558\uC138\uC694.");
6774
+ }
6775
+ );
6776
+ await syncHandoff(cwd).catch(() => {
5935
6777
  });
5936
- return {
5937
- runner,
5938
- passed: true,
5939
- output: stdout || stderr,
5940
- duration: Date.now() - start
5941
- };
5942
6778
  } catch (err) {
5943
- const output = err instanceof Error ? err.message : String(err);
5944
- return {
5945
- runner,
5946
- passed: false,
5947
- output,
5948
- duration: Date.now() - start
5949
- };
6779
+ handleRobocoError(err);
5950
6780
  }
5951
6781
  }
5952
- var init_runner = __esm({
5953
- "src/stages/validation/runner.ts"() {
5954
- "use strict";
5955
- }
5956
- });
5957
-
5958
- // src/stages/validation/harness.ts
5959
- import { join as join15 } from "path";
5960
- import fs21 from "fs-extra";
5961
- async function runHarnessCheck(cwd) {
5962
- const harnessPath = join15(cwd, ".pai", "harness.json");
5963
- if (!await fs21.pathExists(harnessPath)) {
5964
- return { enabled: false, specFile: null, rules: [], checks: [] };
5965
- }
5966
- let config;
6782
+ async function designValidateCommand(cwd, options = {}) {
5967
6783
  try {
5968
- config = await fs21.readJson(harnessPath);
5969
- } catch {
5970
- return { enabled: false, specFile: null, rules: [], checks: [] };
5971
- }
5972
- const specFile = config.specFile ?? "docs/openspec.md";
5973
- const rules = config.rules ?? [];
5974
- const checks = [];
5975
- if (rules.includes("spec-implementation-match")) {
5976
- const specExists = await fs21.pathExists(join15(cwd, specFile));
5977
- const srcExists = await fs21.pathExists(join15(cwd, "src"));
5978
- checks.push({
5979
- rule: "spec-implementation-match",
5980
- passed: specExists && srcExists,
5981
- detail: specExists && srcExists ? "\uC124\uACC4 \uBB38\uC11C\uC640 \uC18C\uC2A4 \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC" : `${!specExists ? specFile + " \uC5C6\uC74C" : ""} ${!srcExists ? "src/ \uC5C6\uC74C" : ""}`.trim()
5982
- });
5983
- }
5984
- if (rules.includes("api-contract-test")) {
5985
- const testDir = await fs21.pathExists(join15(cwd, "tests"));
5986
- const testDir2 = await fs21.pathExists(join15(cwd, "test"));
5987
- checks.push({
5988
- rule: "api-contract-test",
5989
- passed: testDir || testDir2,
5990
- detail: testDir || testDir2 ? "\uD14C\uC2A4\uD2B8 \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC" : "\uD14C\uC2A4\uD2B8 \uB514\uB809\uD1A0\uB9AC(tests/ \uB610\uB294 test/) \uC5C6\uC74C"
5991
- });
5992
- }
5993
- return { enabled: true, specFile, rules, checks };
5994
- }
5995
- var init_harness = __esm({
5996
- "src/stages/validation/harness.ts"() {
5997
- "use strict";
5998
- }
5999
- });
6000
-
6001
- // src/cli/commands/validate.cmd.ts
6002
- var validate_cmd_exports = {};
6003
- __export(validate_cmd_exports, {
6004
- validateCommand: () => validateCommand
6005
- });
6006
- async function validateCommand(cwd) {
6007
- section("\uD14C\uC2A4\uD2B8 \uC2E4\uD589");
6008
- const testResult = await runTests(cwd);
6009
- if (testResult.passed) {
6010
- success(`\uD14C\uC2A4\uD2B8 \uD1B5\uACFC (${testResult.runner}, ${testResult.duration}ms)`);
6011
- } else {
6012
- error("\uD14C\uC2A4\uD2B8 \uC2E4\uD328");
6013
- info(testResult.output.slice(0, 300));
6014
- }
6015
- section("\uD558\uB124\uC2A4 \uAC80\uC99D");
6016
- const harness = await runHarnessCheck(cwd);
6017
- if (!harness.enabled) {
6018
- info("Harness \uC124\uC815 \uC5C6\uC74C \u2014 \uAC74\uB108\uB700");
6019
- info("\uC124\uC815 \uCD94\uAC00: `pai env setup` \uC5D0\uC11C Harness Engineering \uC120\uD0DD");
6020
- } else {
6021
- for (const check of harness.checks) {
6022
- if (check.passed) {
6023
- success(`${check.rule}: ${check.detail}`);
6024
- } else {
6025
- warn(`${check.rule}: ${check.detail}`);
6784
+ await withRoboco(
6785
+ cwd,
6786
+ "design",
6787
+ "pai design validate",
6788
+ { skipGates: options.skipGates, force: options.force, gate: STAGE_GATES.design },
6789
+ async () => {
6790
+ section("PRD \uC644\uC131\uB3C4 \uAC80\uC99D");
6791
+ const result = await validateOpenSpec(cwd);
6792
+ info(`\uC139\uC158 \uC644\uC131\uB3C4: ${result.filledSections}/${result.totalSections}`);
6793
+ if (result.missing.length > 0) {
6794
+ console.log("");
6795
+ warn("\uBBF8\uC791\uC131 \uC139\uC158:");
6796
+ for (const m of result.missing) warn(` - ${m}`);
6797
+ }
6798
+ for (const w of result.warnings) warn(w);
6799
+ if (result.complete) {
6800
+ console.log("");
6801
+ success("PRD \uC644\uC131\uB3C4 \uAC80\uC99D \uD1B5\uACFC!");
6802
+ } else {
6803
+ console.log("");
6804
+ info("docs/openspec.md\uB97C \uC5F4\uC5B4 \uBBF8\uC791\uC131 \uC139\uC158\uC744 \uCC44\uC6CC\uC8FC\uC138\uC694.");
6805
+ }
6026
6806
  }
6027
- }
6028
- }
6029
- const allPassed = testResult.passed && harness.checks.every((c2) => c2.passed);
6030
- console.log("");
6031
- if (allPassed) {
6032
- success("\uAC80\uC99D \uD1B5\uACFC!");
6033
- } else if (!testResult.passed) {
6034
- error("\uAC80\uC99D \uC2E4\uD328 \u2014 \uD14C\uC2A4\uD2B8\uB97C \uC218\uC815\uD558\uC138\uC694.");
6035
- process.exitCode = 1;
6036
- } else {
6037
- warn("\uBD80\uBD84 \uD1B5\uACFC \u2014 \uD558\uB124\uC2A4 \uAC80\uC99D \uD56D\uBAA9\uC744 \uD655\uC778\uD558\uC138\uC694.");
6807
+ );
6808
+ await syncHandoff(cwd).catch(() => {
6809
+ });
6810
+ } catch (err) {
6811
+ handleRobocoError(err);
6038
6812
  }
6039
6813
  }
6040
- var init_validate_cmd = __esm({
6041
- "src/cli/commands/validate.cmd.ts"() {
6814
+ var init_design_cmd = __esm({
6815
+ "src/cli/commands/design.cmd.ts"() {
6042
6816
  "use strict";
6043
6817
  init_ui();
6044
- init_runner();
6045
- init_harness();
6818
+ init_openspec();
6819
+ init_omc();
6820
+ init_config();
6821
+ init_roboco();
6822
+ init_gates();
6823
+ init_validate_cmd();
6046
6824
  }
6047
6825
  });
6048
6826
 
@@ -6098,13 +6876,13 @@ var init_context = __esm({
6098
6876
 
6099
6877
  // src/stages/design/index.ts
6100
6878
  import { join as join16 } from "path";
6101
- import fs22 from "fs-extra";
6879
+ import fs24 from "fs-extra";
6102
6880
  async function autoInstallHarness(cwd) {
6103
6881
  const harnessPath = join16(cwd, ".pai", "harness.json");
6104
- if (await fs22.pathExists(harnessPath)) return;
6882
+ if (await fs24.pathExists(harnessPath)) return;
6105
6883
  await withSpinner("Harness Engineering \uC790\uB3D9 \uC124\uC815 \uC911...", async () => {
6106
- await fs22.ensureDir(join16(cwd, ".pai"));
6107
- await fs22.writeJson(harnessPath, {
6884
+ await fs24.ensureDir(join16(cwd, ".pai"));
6885
+ await fs24.writeJson(harnessPath, {
6108
6886
  version: "1.0",
6109
6887
  specFile: "docs/openspec.md",
6110
6888
  checkOn: ["pre-commit", "ci"],
@@ -6424,22 +7202,41 @@ __export(pipeline_cmd_exports, {
6424
7202
  });
6425
7203
  async function pipelineCommand(cwd, options) {
6426
7204
  printBanner();
6427
- const config = await loadConfig(cwd) ?? createDefaultConfig("my-project", "mockup");
6428
- const pipelineOpts = {};
6429
- if (options.from) {
6430
- pipelineOpts.from = options.from;
6431
- }
6432
- if (options.only) {
6433
- pipelineOpts.only = options.only.split(",").map((s) => s.trim());
7205
+ try {
7206
+ await withRoboco(
7207
+ cwd,
7208
+ "environment",
7209
+ "pai run",
7210
+ {
7211
+ skipGates: options.skipGates,
7212
+ force: options.force,
7213
+ ttlMs: PIPELINE_TTL_MS
7214
+ },
7215
+ async () => {
7216
+ const config = await loadConfig(cwd) ?? createDefaultConfig("my-project", "prototype");
7217
+ const pipelineOpts = {};
7218
+ if (options.from) pipelineOpts.from = options.from;
7219
+ if (options.only) pipelineOpts.only = options.only.split(",").map((s) => s.trim());
7220
+ await runPipeline(cwd, config, pipelineOpts);
7221
+ }
7222
+ );
7223
+ await syncHandoff(cwd).catch(() => {
7224
+ });
7225
+ } catch (err) {
7226
+ handleRobocoError(err);
6434
7227
  }
6435
- await runPipeline(cwd, config, pipelineOpts);
6436
7228
  }
7229
+ var PIPELINE_TTL_MS;
6437
7230
  var init_pipeline_cmd = __esm({
6438
7231
  "src/cli/commands/pipeline.cmd.ts"() {
6439
7232
  "use strict";
7233
+ init_ui();
6440
7234
  init_pipeline();
6441
7235
  init_config();
6442
7236
  init_ui();
7237
+ init_roboco();
7238
+ init_validate_cmd();
7239
+ PIPELINE_TTL_MS = 30 * 60 * 1e3;
6443
7240
  }
6444
7241
  });
6445
7242
 
@@ -6461,7 +7258,7 @@ __export(remove_cmd_exports, {
6461
7258
  removeCommand: () => removeCommand
6462
7259
  });
6463
7260
  import { basename as basename4, dirname } from "path";
6464
- import fs23 from "fs-extra";
7261
+ import fs25 from "fs-extra";
6465
7262
  async function removeCommand(cwd, options) {
6466
7263
  section("\uD504\uB85C\uC81D\uD2B8 \uC0AD\uC81C");
6467
7264
  const config = await loadConfig(cwd);
@@ -6479,7 +7276,7 @@ async function removeCommand(cwd, options) {
6479
7276
  console.log(colors.err(` ${folderName}/ \uD3F4\uB354 \uC804\uCCB4\uAC00 \uC0AD\uC81C\uB429\uB2C8\uB2E4.`));
6480
7277
  hint("\uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
6481
7278
  console.log("");
6482
- const items = await fs23.readdir(cwd);
7279
+ const items = await fs25.readdir(cwd);
6483
7280
  const fileCount = items.filter((i) => !i.startsWith(".")).length;
6484
7281
  const hiddenCount = items.filter((i) => i.startsWith(".")).length;
6485
7282
  info(`\uD30C\uC77C/\uD3F4\uB354 ${fileCount}\uAC1C, \uC228\uAE40 \uD56D\uBAA9 ${hiddenCount}\uAC1C`);
@@ -6498,7 +7295,7 @@ async function removeCommand(cwd, options) {
6498
7295
  }
6499
7296
  process.chdir(parentDir);
6500
7297
  try {
6501
- await fs23.remove(cwd);
7298
+ await fs25.remove(cwd);
6502
7299
  console.log("");
6503
7300
  success(`${folderName}/ \uD504\uB85C\uC81D\uD2B8\uAC00 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
6504
7301
  try {
@@ -6777,7 +7574,7 @@ __export(savetoken_cmd_exports, {
6777
7574
  savetokenCommand: () => savetokenCommand
6778
7575
  });
6779
7576
  import { join as join17, relative } from "path";
6780
- import fs24 from "fs-extra";
7577
+ import fs26 from "fs-extra";
6781
7578
  import chalk7 from "chalk";
6782
7579
  async function savetokenCommand(cwd) {
6783
7580
  const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
@@ -6847,10 +7644,10 @@ async function savetokenCommand(cwd) {
6847
7644
  console.log(` ${chalk7.red("\u25CF")} \uB192\uC74C \uCF54\uB4DC \uC0DD\uC131, \uBCF5\uC7A1\uD55C \uCD94\uB860, \uCC3D\uC758\uC801 \uC0DD\uC131`);
6848
7645
  console.log(` \u2192 ${colors.dim("AI \uD544\uC218 \u2014 \uD504\uB86C\uD504\uD2B8 \uCD5C\uC801\uD654\uB85C \uD1A0\uD070 \uC808\uAC10")}`);
6849
7646
  const reportDir = join17(cwd, ".pai");
6850
- await fs24.ensureDir(reportDir);
7647
+ await fs26.ensureDir(reportDir);
6851
7648
  const report = buildReport(callSites, cwd);
6852
7649
  const reportPath = join17(reportDir, "savetoken-report.md");
6853
- await fs24.writeFile(reportPath, report, "utf8");
7650
+ await fs26.writeFile(reportPath, report, "utf8");
6854
7651
  console.log("");
6855
7652
  success("\uC2A4\uCE94 \uB9AC\uD3EC\uD2B8 \uC800\uC7A5: .pai/savetoken-report.md");
6856
7653
  console.log("");
@@ -7008,7 +7805,7 @@ __export(wakeup_cmd_exports, {
7008
7805
  });
7009
7806
  import { join as join18 } from "path";
7010
7807
  import { homedir as homedir3, platform as osPlatform } from "os";
7011
- import fs25 from "fs-extra";
7808
+ import fs27 from "fs-extra";
7012
7809
  import chalk8 from "chalk";
7013
7810
  async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
7014
7811
  if (timeOrAction === "off") {
@@ -7055,9 +7852,9 @@ async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
7055
7852
  projectDir,
7056
7853
  launchMode
7057
7854
  };
7058
- await fs25.ensureDir(PAI_DIR2);
7059
- await fs25.writeJson(CONFIG_FILE2, config, { spaces: 2 });
7060
- await fs25.writeJson(MESSAGES_FILE, MESSAGES);
7855
+ await fs27.ensureDir(PAI_DIR2);
7856
+ await fs27.writeJson(CONFIG_FILE2, config, { spaces: 2 });
7857
+ await fs27.writeJson(MESSAGES_FILE, MESSAGES);
7061
7858
  await createWakeupScript(config);
7062
7859
  if (osPlatform() === "darwin") {
7063
7860
  await setupMacOS(config);
@@ -7088,7 +7885,7 @@ async function setupMacOS(config) {
7088
7885
  const { execa } = await import("execa");
7089
7886
  const [hour, minute] = config.time.split(":").map(Number);
7090
7887
  const plistDir = join18(homedir3(), "Library", "LaunchAgents");
7091
- await fs25.ensureDir(plistDir);
7888
+ await fs27.ensureDir(plistDir);
7092
7889
  const weekdays = scheduleToWeekdays(config.schedule);
7093
7890
  let calendarEntries;
7094
7891
  if (weekdays.length === 7) {
@@ -7124,7 +7921,7 @@ ${calendarEntries}
7124
7921
  <string>${PAI_DIR2}/wakeup.log</string>
7125
7922
  </dict>
7126
7923
  </plist>`;
7127
- await fs25.writeFile(PLIST_PATH, plist);
7924
+ await fs27.writeFile(PLIST_PATH, plist);
7128
7925
  await execa("launchctl", ["unload", PLIST_PATH]).catch(() => {
7129
7926
  });
7130
7927
  await execa("launchctl", ["load", PLIST_PATH]);
@@ -7147,7 +7944,7 @@ async function setupWindows(config) {
7147
7944
  const { execa } = await import("execa");
7148
7945
  const [hour, minute] = config.time.split(":").map(Number);
7149
7946
  const psScriptDir = join18(homedir3(), ".pai");
7150
- await fs25.ensureDir(psScriptDir);
7947
+ await fs27.ensureDir(psScriptDir);
7151
7948
  const psScriptPath = join18(psScriptDir, "wakeup.ps1");
7152
7949
  const claudeCmd = config.launchMode === "yolo" ? "claude --dangerously-skip-permissions" : "claude";
7153
7950
  const psScript = `# PAI Wakeup \u2014 Claude Code \uC138\uC158 \uC790\uB3D9 \uC2DC\uC791
@@ -7177,7 +7974,7 @@ $notifier.Show([Windows.UI.Notifications.ToastNotification]::new($xml))
7177
7974
  # Open PowerShell with Claude Code
7178
7975
  Start-Process powershell -ArgumentList "-NoExit", "-Command", "Get-Content '$todayFile'; Write-Host ''; Set-Location '${config.projectDir}'; ${claudeCmd}"
7179
7976
  `;
7180
- await fs25.writeFile(psScriptPath, psScript, "utf8");
7977
+ await fs27.writeFile(psScriptPath, psScript, "utf8");
7181
7978
  const daysMap = {
7182
7979
  "\uD3C9\uC77C": "MON,TUE,WED,THU,FRI",
7183
7980
  "\uB9E4\uC77C": "MON,TUE,WED,THU,FRI,SAT,SUN",
@@ -7225,7 +8022,7 @@ async function disableWakeup() {
7225
8022
  if (osPlatform() === "darwin") {
7226
8023
  await execa("launchctl", ["unload", PLIST_PATH]).catch(() => {
7227
8024
  });
7228
- await fs25.remove(PLIST_PATH).catch(() => {
8025
+ await fs27.remove(PLIST_PATH).catch(() => {
7229
8026
  });
7230
8027
  success("launchd \uC2A4\uCF00\uC904 \uC81C\uAC70");
7231
8028
  console.log("");
@@ -7246,14 +8043,14 @@ async function disableWakeup() {
7246
8043
  } else {
7247
8044
  await removeCronEntry();
7248
8045
  }
7249
- await fs25.remove(CONFIG_FILE2).catch(() => {
8046
+ await fs27.remove(CONFIG_FILE2).catch(() => {
7250
8047
  });
7251
8048
  console.log("");
7252
8049
  success("\u2600\uFE0F \uC6E8\uC774\uD06C\uC5C5 \uD574\uC81C \uC644\uB8CC");
7253
8050
  }
7254
8051
  async function showStatus() {
7255
- if (await fs25.pathExists(CONFIG_FILE2)) {
7256
- const config = await fs25.readJson(CONFIG_FILE2);
8052
+ if (await fs27.pathExists(CONFIG_FILE2)) {
8053
+ const config = await fs27.readJson(CONFIG_FILE2);
7257
8054
  console.log("");
7258
8055
  success("\u2600\uFE0F \uC6E8\uC774\uD06C\uC5C5 \uD65C\uC131\uD654");
7259
8056
  console.log(` \uC2DC\uAC04 ${chalk8.white(config.time)}`);
@@ -7261,7 +8058,7 @@ async function showStatus() {
7261
8058
  console.log(` \uD504\uB85C\uC81D\uD2B8 ${chalk8.white(config.projectDir)}`);
7262
8059
  console.log(` \uBAA8\uB4DC ${chalk8.white(config.launchMode === "yolo" ? "claude-YOLO mode" : "\uC77C\uBC18 \uBAA8\uB4DC")}`);
7263
8060
  if (osPlatform() === "darwin") {
7264
- const plistExists = await fs25.pathExists(PLIST_PATH);
8061
+ const plistExists = await fs27.pathExists(PLIST_PATH);
7265
8062
  console.log(` launchd ${plistExists ? chalk8.green("\uD65C\uC131") : chalk8.red("\uBE44\uD65C\uC131")}`);
7266
8063
  }
7267
8064
  console.log("");
@@ -7414,7 +8211,7 @@ fi
7414
8211
 
7415
8212
  echo "[$(date)] PAI Wakeup completed" >> "$LOG_FILE"
7416
8213
  `;
7417
- await fs25.writeFile(SCRIPT_FILE, script, { mode: 493 });
8214
+ await fs27.writeFile(SCRIPT_FILE, script, { mode: 493 });
7418
8215
  }
7419
8216
  var PAI_DIR2, CONFIG_FILE2, MESSAGES_FILE, SCRIPT_FILE, PLIST_NAME, PLIST_PATH, CRON_MARKER, MESSAGES;
7420
8217
  var init_wakeup_cmd = __esm({
@@ -7514,9 +8311,9 @@ function createProgram() {
7514
8311
  const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init_cmd(), init_cmd_exports));
7515
8312
  await initCommand2(process.cwd(), projectName);
7516
8313
  });
7517
- program2.command("add").description("\uD50C\uB7EC\uADF8\uC778 \uCD94\uAC00 (Mockup \u2192 Production \uD655\uC7A5)").action(async () => {
8314
+ program2.command("add").description("\uD50C\uB7EC\uADF8\uC778 \uCD94\uAC00 (Mockup \u2192 Production \uD655\uC7A5)").option("--skip-gates", "\uAC8C\uC774\uD2B8 \uACBD\uACE0\uB9CC \uCD9C\uB825\uD558\uACE0 \uC9C4\uD589").option("--force", "\uAC8C\uC774\uD2B8 \uC804\uBA74 \uBB34\uC2DC").action(async (options) => {
7518
8315
  const { addCommand } = await Promise.resolve().then(() => (init_add_cmd(), add_cmd_exports));
7519
- await addCommand(process.cwd());
8316
+ await addCommand(process.cwd(), options);
7520
8317
  });
7521
8318
  program2.command("check").description("\uD658\uACBD \uC810\uAC80 (Node / Git / Claude Code)").action(async () => {
7522
8319
  const { checkCommand: checkCommand2 } = await Promise.resolve().then(() => (init_check_cmd(), check_cmd_exports));
@@ -7531,23 +8328,23 @@ function createProgram() {
7531
8328
  await helpCommand2();
7532
8329
  });
7533
8330
  const design = program2.command("design").description("\uC124\uACC4 \uAD00\uB9AC (OpenSpec / OMC)");
7534
- design.command("init").description("PRD/OMC \uD15C\uD50C\uB9BF \uC0DD\uC131").action(async () => {
8331
+ design.command("init").description("PRD/OMC \uD15C\uD50C\uB9BF \uC0DD\uC131").option("--skip-gates", "\uAC8C\uC774\uD2B8 \uACBD\uACE0\uB9CC \uCD9C\uB825\uD558\uACE0 \uC9C4\uD589").option("--force", "\uAC8C\uC774\uD2B8 \uC804\uBA74 \uBB34\uC2DC").action(async (options) => {
7535
8332
  const { designInitCommand: designInitCommand2 } = await Promise.resolve().then(() => (init_design_cmd(), design_cmd_exports));
7536
- await designInitCommand2(process.cwd());
8333
+ await designInitCommand2(process.cwd(), options);
7537
8334
  });
7538
- design.command("validate").description("PRD \uC644\uC131\uB3C4 \uAC80\uC99D").action(async () => {
8335
+ design.command("validate").description("PRD \uC644\uC131\uB3C4 \uAC80\uC99D").option("--skip-gates", "\uAC8C\uC774\uD2B8 \uACBD\uACE0\uB9CC \uCD9C\uB825\uD558\uACE0 \uC9C4\uD589").option("--force", "\uAC8C\uC774\uD2B8 \uC804\uBA74 \uBB34\uC2DC").action(async (options) => {
7539
8336
  const { designValidateCommand: designValidateCommand2 } = await Promise.resolve().then(() => (init_design_cmd(), design_cmd_exports));
7540
- await designValidateCommand2(process.cwd());
8337
+ await designValidateCommand2(process.cwd(), options);
7541
8338
  });
7542
- program2.command("test").description("\uD14C\uC2A4\uD2B8 / \uD558\uB124\uC2A4 \uAC80\uC99D \uC2E4\uD589").action(async () => {
8339
+ program2.command("test").description("\uD14C\uC2A4\uD2B8 / \uD558\uB124\uC2A4 \uAC80\uC99D \uC2E4\uD589").option("--skip-gates", "\uAC8C\uC774\uD2B8 \uACBD\uACE0\uB9CC \uCD9C\uB825\uD558\uACE0 \uC9C4\uD589").option("--force", "\uAC8C\uC774\uD2B8 \uC804\uBA74 \uBB34\uC2DC").action(async (options) => {
7543
8340
  const { testCommand } = await Promise.resolve().then(() => (init_test_cmd(), test_cmd_exports));
7544
- await testCommand(process.cwd());
8341
+ await testCommand(process.cwd(), options);
7545
8342
  });
7546
- program2.command("grade").description("AI \uAC1C\uBC1C \uC900\uBE44\uB3C4 \uD3C9\uAC00 (6\uCE74\uD14C\uACE0\uB9AC \uC810\uC218)").option("--fail-under <score>", "\uCD5C\uC18C \uC810\uC218 (\uBBF8\uB2EC \uC2DC exit 1)", parseInt).option("--verbose", "\uC0C1\uC138 findings \uCD9C\uB825").option("-o, --output <file>", "\uACB0\uACFC\uB97C \uD30C\uC77C\uB85C \uC800\uC7A5").option("--no-cache", "\uCE90\uC2DC \uBB34\uC2DC\uD558\uACE0 \uC0C8\uB85C \uBD84\uC11D").action(async (options) => {
8343
+ program2.command("grade").description("AI \uAC1C\uBC1C \uC900\uBE44\uB3C4 \uD3C9\uAC00 (6\uCE74\uD14C\uACE0\uB9AC \uC810\uC218)").option("--fail-under <score>", "\uCD5C\uC18C \uC810\uC218 (\uBBF8\uB2EC \uC2DC exit 1)", parseInt).option("--verbose", "\uC0C1\uC138 findings \uCD9C\uB825").option("-o, --output <file>", "\uACB0\uACFC\uB97C \uD30C\uC77C\uB85C \uC800\uC7A5").option("--no-cache", "\uCE90\uC2DC \uBB34\uC2DC\uD558\uACE0 \uC0C8\uB85C \uBD84\uC11D").option("--skip-gates", "\uAC8C\uC774\uD2B8 \uACBD\uACE0\uB9CC \uCD9C\uB825\uD558\uACE0 \uC9C4\uD589").option("--force", "\uAC8C\uC774\uD2B8 \uC804\uBA74 \uBB34\uC2DC (\uCD5C\uADFC validation \uC5C6\uC5B4\uB3C4 \uC2E4\uD589)").action(async (options) => {
7547
8344
  const { gradeCommand } = await Promise.resolve().then(() => (init_grade_cmd(), grade_cmd_exports));
7548
8345
  await gradeCommand(process.cwd(), options);
7549
8346
  });
7550
- program2.command("run").description("\uC804\uCCB4 5\uB2E8\uACC4 \uD30C\uC774\uD504\uB77C\uC778 \uC2E4\uD589").option("--from <stage>", "\uD2B9\uC815 \uB2E8\uACC4\uBD80\uD130 \uC2E4\uD589").option("--only <stages>", "\uC120\uD0DD\uC801 \uB2E8\uACC4 \uC2E4\uD589 (\uC27C\uD45C \uAD6C\uBD84)").action(async (options) => {
8347
+ program2.command("run").description("\uC804\uCCB4 5\uB2E8\uACC4 \uD30C\uC774\uD504\uB77C\uC778 \uC2E4\uD589").option("--from <stage>", "\uD2B9\uC815 \uB2E8\uACC4\uBD80\uD130 \uC2E4\uD589").option("--only <stages>", "\uC120\uD0DD\uC801 \uB2E8\uACC4 \uC2E4\uD589 (\uC27C\uD45C \uAD6C\uBD84)").option("--skip-gates", "\uAC8C\uC774\uD2B8 \uACBD\uACE0\uB9CC \uCD9C\uB825\uD558\uACE0 \uC9C4\uD589").option("--force", "\uAC8C\uC774\uD2B8 \uC804\uBA74 \uBB34\uC2DC").action(async (options) => {
7551
8348
  const { runCommand } = await Promise.resolve().then(() => (init_run_cmd(), run_cmd_exports));
7552
8349
  await runCommand(process.cwd(), options);
7553
8350
  });