pai-zero 0.9.1 → 0.10.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
@@ -501,6 +501,37 @@ async function interactiveInterview(analysis, _cwd, projectName) {
501
501
  if (database === "supabase") extraTools.push("supabase");
502
502
  extraTools.push("gstack", "harness");
503
503
  }
504
+ let mcp;
505
+ console.log("");
506
+ const { mcpDev } = await inquirer.prompt([{
507
+ type: "confirm",
508
+ name: "mcpDev",
509
+ message: "MCP(Model Context Protocol) \uC11C\uBC84\uB97C \uAC1C\uBC1C\uD558\uC2DC\uB098\uC694?",
510
+ default: false
511
+ }]);
512
+ if (mcpDev) {
513
+ console.log("");
514
+ const { mcpType } = await inquirer.prompt([{
515
+ type: "list",
516
+ name: "mcpType",
517
+ message: "MCP \uC11C\uBC84 \uC720\uD615:",
518
+ choices: [
519
+ { name: `Tools \uC11C\uBC84 ${colors.dim("\u2500 AI\uAC00 \uD638\uCD9C\uD560 \uAE30\uB2A5 \uC81C\uACF5")}`, value: "tools" },
520
+ { name: `Resources \uC11C\uBC84 ${colors.dim("\u2500 AI\uAC00 \uC77D\uC744 \uCEE8\uD14D\uC2A4\uD2B8 \uC81C\uACF5")}`, value: "resources" },
521
+ { name: `Prompts \uC11C\uBC84 ${colors.dim("\u2500 \uC7AC\uC0AC\uC6A9 \uD504\uB86C\uD504\uD2B8 \uD15C\uD50C\uB9BF")}`, value: "prompts" },
522
+ { name: `All-in-one ${colors.dim("\u2500 \uBAA8\uB450 \uD3EC\uD568")}`, value: "all" }
523
+ ]
524
+ }]);
525
+ const { mcpName } = await inquirer.prompt([{
526
+ type: "input",
527
+ name: "mcpName",
528
+ message: "MCP \uC11C\uBC84 \uC774\uB984:",
529
+ default: `${projectName}-mcp`,
530
+ validate: (v) => /^[a-z0-9][a-z0-9-]*$/.test(v.trim()) ? true : "\uC601\uBB38 \uC18C\uBB38\uC790, \uC22B\uC790, \uD558\uC774\uD508(-)\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
531
+ }]);
532
+ mcp = { enabled: true, type: mcpType, name: mcpName.trim() };
533
+ extraTools.push("mcp");
534
+ }
504
535
  step(3, 3, "\uC124\uCE58 \uD655\uC778");
505
536
  console.log("");
506
537
  console.log(colors.dim(" \uC124\uCE58\uB420 \uD56D\uBAA9"));
@@ -516,6 +547,9 @@ async function interactiveInterview(analysis, _cwd, projectName) {
516
547
  if (extraTools.includes("supabase")) console.log(` ${colors.accent("+")} Supabase \uB370\uC774\uD130\uBCA0\uC774\uC2A4`);
517
548
  if (extraTools.includes("gstack")) console.log(` ${colors.accent("+")} gstack \uD14C\uC2A4\uD2B8 \uC790\uB3D9\uD654`);
518
549
  if (extraTools.includes("harness")) console.log(` ${colors.accent("+")} Harness \uD488\uC9C8 \uAC80\uC99D`);
550
+ if (extraTools.includes("mcp") && mcp) {
551
+ console.log(` ${colors.accent("+")} MCP \uC11C\uBC84 (${mcp.name}, ${mcp.type})`);
552
+ }
519
553
  }
520
554
  if (authMethods.length > 0) {
521
555
  console.log("");
@@ -541,6 +575,7 @@ async function interactiveInterview(analysis, _cwd, projectName) {
541
575
  authMethods,
542
576
  customAuth,
543
577
  extraTools,
578
+ mcp,
544
579
  setupDomains: {
545
580
  claudeEnv: true,
546
581
  processDocs: true,
@@ -915,18 +950,348 @@ var init_platform = __esm({
915
950
  }
916
951
  });
917
952
 
953
+ // src/stages/environment/provisioners/mcp.ts
954
+ var mcp_exports = {};
955
+ __export(mcp_exports, {
956
+ provisionMcp: () => provisionMcp
957
+ });
958
+ import { join as join3 } from "path";
959
+ import fs4 from "fs-extra";
960
+ async function provisionMcp(ctx) {
961
+ const mcp = ctx.mcp;
962
+ if (!mcp?.enabled) return;
963
+ const mcpDir = join3(ctx.cwd, "mcp-server");
964
+ await fs4.ensureDir(mcpDir);
965
+ await fs4.ensureDir(join3(mcpDir, "src"));
966
+ await fs4.ensureDir(join3(mcpDir, "tests"));
967
+ const pkgJson = {
968
+ name: mcp.name,
969
+ version: "0.1.0",
970
+ description: `MCP server for ${ctx.projectName}`,
971
+ type: "module",
972
+ main: "dist/index.js",
973
+ bin: { [mcp.name]: "./dist/index.js" },
974
+ scripts: {
975
+ build: "tsc",
976
+ dev: "tsc --watch",
977
+ test: "vitest run",
978
+ start: "node dist/index.js"
979
+ },
980
+ dependencies: {
981
+ "@modelcontextprotocol/sdk": "^1.0.0"
982
+ },
983
+ devDependencies: {
984
+ typescript: "^5.7.0",
985
+ "@types/node": "^20.17.0",
986
+ vitest: "^3.0.0"
987
+ }
988
+ };
989
+ const pkgPath = join3(mcpDir, "package.json");
990
+ if (!await fs4.pathExists(pkgPath)) {
991
+ await fs4.writeJson(pkgPath, pkgJson, { spaces: 2 });
992
+ }
993
+ const tsconfig = {
994
+ compilerOptions: {
995
+ target: "ES2022",
996
+ module: "ESNext",
997
+ moduleResolution: "bundler",
998
+ outDir: "./dist",
999
+ rootDir: "./src",
1000
+ strict: true,
1001
+ esModuleInterop: true,
1002
+ skipLibCheck: true,
1003
+ resolveJsonModule: true,
1004
+ declaration: true
1005
+ },
1006
+ include: ["src/**/*"],
1007
+ exclude: ["node_modules", "dist", "tests"]
1008
+ };
1009
+ const tsPath = join3(mcpDir, "tsconfig.json");
1010
+ if (!await fs4.pathExists(tsPath)) {
1011
+ await fs4.writeJson(tsPath, tsconfig, { spaces: 2 });
1012
+ }
1013
+ const indexPath = join3(mcpDir, "src", "index.ts");
1014
+ if (!await fs4.pathExists(indexPath)) {
1015
+ await fs4.writeFile(indexPath, buildIndexTs(mcp), "utf8");
1016
+ }
1017
+ if (mcp.type === "tools" || mcp.type === "all") {
1018
+ const toolsPath = join3(mcpDir, "src", "tools.ts");
1019
+ if (!await fs4.pathExists(toolsPath)) {
1020
+ await fs4.writeFile(toolsPath, TOOLS_TEMPLATE, "utf8");
1021
+ }
1022
+ }
1023
+ if (mcp.type === "resources" || mcp.type === "all") {
1024
+ const resPath = join3(mcpDir, "src", "resources.ts");
1025
+ if (!await fs4.pathExists(resPath)) {
1026
+ await fs4.writeFile(resPath, RESOURCES_TEMPLATE, "utf8");
1027
+ }
1028
+ }
1029
+ if (mcp.type === "prompts" || mcp.type === "all") {
1030
+ const promptsPath = join3(mcpDir, "src", "prompts.ts");
1031
+ if (!await fs4.pathExists(promptsPath)) {
1032
+ await fs4.writeFile(promptsPath, PROMPTS_TEMPLATE, "utf8");
1033
+ }
1034
+ }
1035
+ const testPath = join3(mcpDir, "tests", "server.test.ts");
1036
+ if (!await fs4.pathExists(testPath)) {
1037
+ await fs4.writeFile(testPath, TEST_TEMPLATE, "utf8");
1038
+ }
1039
+ const readmePath = join3(mcpDir, "README.md");
1040
+ if (!await fs4.pathExists(readmePath)) {
1041
+ await fs4.writeFile(readmePath, buildReadme(mcp), "utf8");
1042
+ }
1043
+ const mcpJsonPath = join3(ctx.cwd, ".mcp.json");
1044
+ const mcpJson = await fs4.pathExists(mcpJsonPath) ? await fs4.readJson(mcpJsonPath) : { mcpServers: {} };
1045
+ mcpJson.mcpServers[mcp.name] = {
1046
+ command: "node",
1047
+ args: ["./mcp-server/dist/index.js"]
1048
+ };
1049
+ await fs4.writeJson(mcpJsonPath, mcpJson, { spaces: 2 });
1050
+ const gitignorePath = join3(mcpDir, ".gitignore");
1051
+ if (!await fs4.pathExists(gitignorePath)) {
1052
+ await fs4.writeFile(gitignorePath, "node_modules/\ndist/\n*.log\n", "utf8");
1053
+ }
1054
+ }
1055
+ function buildIndexTs(mcp) {
1056
+ const imports = [];
1057
+ const registrations = [];
1058
+ if (mcp.type === "tools" || mcp.type === "all") {
1059
+ imports.push("import { registerTools } from './tools.js';");
1060
+ registrations.push(" registerTools(server);");
1061
+ }
1062
+ if (mcp.type === "resources" || mcp.type === "all") {
1063
+ imports.push("import { registerResources } from './resources.js';");
1064
+ registrations.push(" registerResources(server);");
1065
+ }
1066
+ if (mcp.type === "prompts" || mcp.type === "all") {
1067
+ imports.push("import { registerPrompts } from './prompts.js';");
1068
+ registrations.push(" registerPrompts(server);");
1069
+ }
1070
+ return `#!/usr/bin/env node
1071
+ /**
1072
+ * ${mcp.name} \u2014 MCP Server
1073
+ * Generated by PAI
1074
+ */
1075
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
1076
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
1077
+ ${imports.join("\n")}
1078
+
1079
+ async function main() {
1080
+ const server = new Server(
1081
+ {
1082
+ name: '${mcp.name}',
1083
+ version: '0.1.0',
1084
+ },
1085
+ {
1086
+ capabilities: {
1087
+ ${mcp.type === "tools" || mcp.type === "all" ? " tools: {}," : ""}
1088
+ ${mcp.type === "resources" || mcp.type === "all" ? " resources: {}," : ""}
1089
+ ${mcp.type === "prompts" || mcp.type === "all" ? " prompts: {}," : ""}
1090
+ },
1091
+ },
1092
+ );
1093
+
1094
+ ${registrations.join("\n")}
1095
+
1096
+ const transport = new StdioServerTransport();
1097
+ await server.connect(transport);
1098
+ console.error('${mcp.name} MCP server running on stdio');
1099
+ }
1100
+
1101
+ main().catch((err) => {
1102
+ console.error('Fatal error:', err);
1103
+ process.exit(1);
1104
+ });
1105
+ `;
1106
+ }
1107
+ function buildReadme(mcp) {
1108
+ return `# ${mcp.name} \u2014 MCP Server
1109
+
1110
+ PAI\uAC00 \uC0DD\uC131\uD55C MCP(Model Context Protocol) \uC11C\uBC84\uC785\uB2C8\uB2E4.
1111
+
1112
+ ## \uD0C0\uC785: ${mcp.type}
1113
+
1114
+ ${mcp.type === "tools" || mcp.type === "all" ? "- **Tools**: AI\uAC00 \uD638\uCD9C\uD560 \uAE30\uB2A5 \uC81C\uACF5 (src/tools.ts)" : ""}
1115
+ ${mcp.type === "resources" || mcp.type === "all" ? "- **Resources**: AI\uAC00 \uC77D\uC744 \uCEE8\uD14D\uC2A4\uD2B8 \uC81C\uACF5 (src/resources.ts)" : ""}
1116
+ ${mcp.type === "prompts" || mcp.type === "all" ? "- **Prompts**: \uC7AC\uC0AC\uC6A9 \uD504\uB86C\uD504\uD2B8 \uD15C\uD50C\uB9BF (src/prompts.ts)" : ""}
1117
+
1118
+ ## \uAC1C\uBC1C
1119
+
1120
+ \`\`\`bash
1121
+ cd mcp-server
1122
+ npm install
1123
+ npm run build
1124
+ npm test
1125
+ \`\`\`
1126
+
1127
+ ## Claude Code \uC5F0\uACB0
1128
+
1129
+ \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC758 \`.mcp.json\` \uD30C\uC77C\uC5D0 \uC774\uBBF8 \uB4F1\uB85D\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.
1130
+ Claude Code\uB97C \uC7AC\uC2DC\uC791\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C MCP \uC11C\uBC84\uAC00 \uB85C\uB4DC\uB429\uB2C8\uB2E4.
1131
+
1132
+ \uC218\uB3D9 \uB4F1\uB85D:
1133
+ \`\`\`bash
1134
+ claude mcp add ${mcp.name} -- node ./mcp-server/dist/index.js
1135
+ \`\`\`
1136
+
1137
+ ## \uB2E4\uC74C \uB2E8\uACC4
1138
+
1139
+ 1. \`npm install\` \u2014 \uC758\uC874\uC131 \uC124\uCE58
1140
+ 2. \`npm run build\` \u2014 TypeScript \uCEF4\uD30C\uC77C
1141
+ 3. \`src/\` \uB514\uB809\uD1A0\uB9AC\uC758 \uD15C\uD50C\uB9BF\uC744 \uC2E4\uC81C \uB85C\uC9C1\uC73C\uB85C \uC218\uC815
1142
+ 4. Claude Code \uC7AC\uC2DC\uC791 \u2192 MCP \uC11C\uBC84 \uC0AC\uC6A9
1143
+
1144
+ ## \uCC38\uACE0 \uBB38\uC11C
1145
+
1146
+ - [MCP \uACF5\uC2DD \uBB38\uC11C](https://modelcontextprotocol.io/)
1147
+ - [SDK \uBB38\uC11C](https://github.com/modelcontextprotocol/sdk)
1148
+ `;
1149
+ }
1150
+ var TOOLS_TEMPLATE, RESOURCES_TEMPLATE, PROMPTS_TEMPLATE, TEST_TEMPLATE;
1151
+ var init_mcp = __esm({
1152
+ "src/stages/environment/provisioners/mcp.ts"() {
1153
+ "use strict";
1154
+ TOOLS_TEMPLATE = `import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
1155
+ import {
1156
+ CallToolRequestSchema,
1157
+ ListToolsRequestSchema,
1158
+ } from '@modelcontextprotocol/sdk/types.js';
1159
+
1160
+ export function registerTools(server: Server): void {
1161
+ // Tool \uBAA9\uB85D \uC815\uC758
1162
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1163
+ tools: [
1164
+ {
1165
+ name: 'hello',
1166
+ description: '\uD14C\uC2A4\uD2B8\uC6A9 \uC778\uC0AC \uB3C4\uAD6C \u2014 \uC774 \uD15C\uD50C\uB9BF\uC744 \uC218\uC815\uD574\uC11C \uC2E4\uC81C \uB3C4\uAD6C\uB97C \uAD6C\uD604\uD558\uC138\uC694',
1167
+ inputSchema: {
1168
+ type: 'object',
1169
+ properties: {
1170
+ name: { type: 'string', description: '\uC778\uC0AC\uD560 \uB300\uC0C1' },
1171
+ },
1172
+ required: ['name'],
1173
+ },
1174
+ },
1175
+ ],
1176
+ }));
1177
+
1178
+ // Tool \uD638\uCD9C \uCC98\uB9AC
1179
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1180
+ const { name, arguments: args } = request.params;
1181
+
1182
+ if (name === 'hello') {
1183
+ const target = (args as { name?: string })?.name ?? 'world';
1184
+ return {
1185
+ content: [{ type: 'text', text: \`Hello, \${target}!\` }],
1186
+ };
1187
+ }
1188
+
1189
+ throw new Error(\`Unknown tool: \${name}\`);
1190
+ });
1191
+ }
1192
+ `;
1193
+ RESOURCES_TEMPLATE = `import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
1194
+ import {
1195
+ ListResourcesRequestSchema,
1196
+ ReadResourceRequestSchema,
1197
+ } from '@modelcontextprotocol/sdk/types.js';
1198
+
1199
+ export function registerResources(server: Server): void {
1200
+ // Resource \uBAA9\uB85D \uC815\uC758
1201
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
1202
+ resources: [
1203
+ {
1204
+ uri: 'example://sample',
1205
+ name: 'Sample Resource',
1206
+ description: '\uC608\uC81C \uB9AC\uC18C\uC2A4 \u2014 AI\uAC00 \uC77D\uC744 \uCEE8\uD14D\uC2A4\uD2B8',
1207
+ mimeType: 'text/plain',
1208
+ },
1209
+ ],
1210
+ }));
1211
+
1212
+ // Resource \uC77D\uAE30
1213
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1214
+ const { uri } = request.params;
1215
+
1216
+ if (uri === 'example://sample') {
1217
+ return {
1218
+ contents: [{
1219
+ uri,
1220
+ mimeType: 'text/plain',
1221
+ text: 'This is a sample resource content.',
1222
+ }],
1223
+ };
1224
+ }
1225
+
1226
+ throw new Error(\`Unknown resource: \${uri}\`);
1227
+ });
1228
+ }
1229
+ `;
1230
+ PROMPTS_TEMPLATE = `import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
1231
+ import {
1232
+ GetPromptRequestSchema,
1233
+ ListPromptsRequestSchema,
1234
+ } from '@modelcontextprotocol/sdk/types.js';
1235
+
1236
+ export function registerPrompts(server: Server): void {
1237
+ // Prompt \uBAA9\uB85D \uC815\uC758
1238
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
1239
+ prompts: [
1240
+ {
1241
+ name: 'review-code',
1242
+ description: '\uCF54\uB4DC \uB9AC\uBDF0 \uC694\uCCAD \uD504\uB86C\uD504\uD2B8',
1243
+ arguments: [
1244
+ { name: 'code', description: '\uB9AC\uBDF0\uD560 \uCF54\uB4DC', required: true },
1245
+ ],
1246
+ },
1247
+ ],
1248
+ }));
1249
+
1250
+ // Prompt \uAC00\uC838\uC624\uAE30
1251
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1252
+ const { name, arguments: args } = request.params;
1253
+
1254
+ if (name === 'review-code') {
1255
+ const code = (args as { code?: string })?.code ?? '';
1256
+ return {
1257
+ messages: [{
1258
+ role: 'user',
1259
+ content: {
1260
+ type: 'text',
1261
+ text: \`\uB2E4\uC74C \uCF54\uB4DC\uB97C \uB9AC\uBDF0\uD574\uC8FC\uC138\uC694:\\n\\n\${code}\`,
1262
+ },
1263
+ }],
1264
+ };
1265
+ }
1266
+
1267
+ throw new Error(\`Unknown prompt: \${name}\`);
1268
+ });
1269
+ }
1270
+ `;
1271
+ TEST_TEMPLATE = `import { describe, it, expect } from 'vitest';
1272
+
1273
+ describe('MCP Server', () => {
1274
+ it('should be tested', () => {
1275
+ // TODO: MCP \uC11C\uBC84 \uD14C\uC2A4\uD2B8 \uC791\uC131
1276
+ expect(true).toBe(true);
1277
+ });
1278
+ });
1279
+ `;
1280
+ }
1281
+ });
1282
+
918
1283
  // src/stages/environment/provisioners/registry.ts
919
1284
  var registry_exports = {};
920
1285
  __export(registry_exports, {
921
1286
  PROVISIONERS: () => PROVISIONERS,
922
1287
  runProvisioners: () => runProvisioners
923
1288
  });
924
- import { join as join3 } from "path";
925
- import fs4 from "fs-extra";
1289
+ import { join as join4 } from "path";
1290
+ import fs5 from "fs-extra";
926
1291
  async function provisionGitHub(ctx) {
927
- const gitDir = join3(ctx.cwd, ".git");
928
- if (!await fs4.pathExists(gitDir)) {
929
- await fs4.ensureDir(join3(ctx.cwd, ".github", "workflows"));
1292
+ const gitDir = join4(ctx.cwd, ".git");
1293
+ if (!await fs5.pathExists(gitDir)) {
1294
+ await fs5.ensureDir(join4(ctx.cwd, ".github", "workflows"));
930
1295
  try {
931
1296
  const { execa } = await import("execa");
932
1297
  await execa("git", ["init"], { cwd: ctx.cwd });
@@ -935,11 +1300,11 @@ async function provisionGitHub(ctx) {
935
1300
  }
936
1301
  }
937
1302
  const dirs = ["src", "docs", "tests", "public", ".pai"];
938
- await Promise.all(dirs.map((d) => fs4.ensureDir(join3(ctx.cwd, d))));
939
- const handoffPath = join3(ctx.cwd, "handoff.md");
940
- if (!await fs4.pathExists(handoffPath)) {
1303
+ await Promise.all(dirs.map((d) => fs5.ensureDir(join4(ctx.cwd, d))));
1304
+ const handoffPath = join4(ctx.cwd, "handoff.md");
1305
+ if (!await fs5.pathExists(handoffPath)) {
941
1306
  const now = (/* @__PURE__ */ new Date()).toLocaleString("ko-KR");
942
- await fs4.writeFile(handoffPath, [
1307
+ await fs5.writeFile(handoffPath, [
943
1308
  `# Handoff \u2014 ${ctx.projectName}`,
944
1309
  "",
945
1310
  `> \uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8: ${now}`,
@@ -968,9 +1333,9 @@ async function provisionGitHub(ctx) {
968
1333
  "- \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"
969
1334
  ].join("\n") + "\n");
970
1335
  }
971
- const contribPath = join3(ctx.cwd, "CONTRIBUTING.md");
972
- if (!await fs4.pathExists(contribPath)) {
973
- await fs4.writeFile(contribPath, [
1336
+ const contribPath = join4(ctx.cwd, "CONTRIBUTING.md");
1337
+ if (!await fs5.pathExists(contribPath)) {
1338
+ await fs5.writeFile(contribPath, [
974
1339
  `# Contributing to ${ctx.projectName}`,
975
1340
  "",
976
1341
  "\uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uAE30\uC5EC\uD574 \uC8FC\uC154\uC11C \uAC10\uC0AC\uD569\uB2C8\uB2E4.",
@@ -1014,11 +1379,11 @@ async function provisionGitHub(ctx) {
1014
1379
  ""
1015
1380
  ].join("\n") + "\n");
1016
1381
  }
1017
- const contribSkillDir = join3(ctx.cwd, ".claude", "skills");
1018
- await fs4.ensureDir(contribSkillDir);
1019
- const contribSkillPath = join3(contribSkillDir, "contributing.md");
1020
- if (!await fs4.pathExists(contribSkillPath)) {
1021
- await fs4.writeFile(contribSkillPath, [
1382
+ const contribSkillDir = join4(ctx.cwd, ".claude", "skills");
1383
+ await fs5.ensureDir(contribSkillDir);
1384
+ const contribSkillPath = join4(contribSkillDir, "contributing.md");
1385
+ if (!await fs5.pathExists(contribSkillPath)) {
1386
+ await fs5.writeFile(contribSkillPath, [
1022
1387
  "---",
1023
1388
  "name: contributing",
1024
1389
  'description: "\uAE30\uC5EC\uC790 \uAC00\uC774\uB4DC \u2014 \uBE0C\uB79C\uCE58 \uADDC\uCE59, \uCEE4\uBC0B \uBA54\uC2DC\uC9C0, PR \uD504\uB85C\uC138\uC2A4 \uC790\uB3D9 \uC801\uC6A9"',
@@ -1043,9 +1408,9 @@ async function provisionGitHub(ctx) {
1043
1408
  ""
1044
1409
  ].join("\n") + "\n");
1045
1410
  }
1046
- const gitignorePath = join3(ctx.cwd, ".gitignore");
1047
- if (!await fs4.pathExists(gitignorePath)) {
1048
- await fs4.writeFile(gitignorePath, [
1411
+ const gitignorePath = join4(ctx.cwd, ".gitignore");
1412
+ if (!await fs5.pathExists(gitignorePath)) {
1413
+ await fs5.writeFile(gitignorePath, [
1049
1414
  "# Dependencies",
1050
1415
  "node_modules/",
1051
1416
  "",
@@ -1090,9 +1455,9 @@ async function provisionGitHub(ctx) {
1090
1455
  }
1091
1456
  }
1092
1457
  async function provisionVercel(ctx) {
1093
- const vercelJson = join3(ctx.cwd, "vercel.json");
1094
- if (!await fs4.pathExists(vercelJson)) {
1095
- await fs4.writeJson(vercelJson, {
1458
+ const vercelJson = join4(ctx.cwd, "vercel.json");
1459
+ if (!await fs5.pathExists(vercelJson)) {
1460
+ await fs5.writeJson(vercelJson, {
1096
1461
  version: 2,
1097
1462
  name: ctx.projectName,
1098
1463
  builds: [{ src: "src/**", use: "@vercel/static" }]
@@ -1100,20 +1465,20 @@ async function provisionVercel(ctx) {
1100
1465
  }
1101
1466
  }
1102
1467
  async function provisionSupabase(ctx) {
1103
- const supabaseDir = join3(ctx.cwd, "supabase");
1104
- await fs4.ensureDir(supabaseDir);
1105
- const configToml = join3(supabaseDir, "config.toml");
1106
- if (!await fs4.pathExists(configToml)) {
1107
- await fs4.writeFile(configToml, '[api]\nschemas = ["public"]\n[auth]\nsite_url = "http://localhost:3000"\n');
1468
+ const supabaseDir = join4(ctx.cwd, "supabase");
1469
+ await fs5.ensureDir(supabaseDir);
1470
+ const configToml = join4(supabaseDir, "config.toml");
1471
+ if (!await fs5.pathExists(configToml)) {
1472
+ await fs5.writeFile(configToml, '[api]\nschemas = ["public"]\n[auth]\nsite_url = "http://localhost:3000"\n');
1108
1473
  }
1109
1474
  ctx.envEntries["NEXT_PUBLIC_SUPABASE_URL"] = "YOUR_SUPABASE_URL";
1110
1475
  ctx.envEntries["NEXT_PUBLIC_SUPABASE_ANON_KEY"] = "YOUR_SUPABASE_ANON_KEY";
1111
1476
  }
1112
1477
  async function provisionOpenSpec(ctx) {
1113
- await fs4.ensureDir(join3(ctx.cwd, "docs"));
1114
- const openspecPath = join3(ctx.cwd, "docs", "openspec.md");
1115
- if (!await fs4.pathExists(openspecPath)) {
1116
- await fs4.writeFile(openspecPath, [
1478
+ await fs5.ensureDir(join4(ctx.cwd, "docs"));
1479
+ const openspecPath = join4(ctx.cwd, "docs", "openspec.md");
1480
+ if (!await fs5.pathExists(openspecPath)) {
1481
+ await fs5.writeFile(openspecPath, [
1117
1482
  `# OpenSpec \u2014 ${ctx.projectName}`,
1118
1483
  "",
1119
1484
  "## 1. \uBAA9\uC801 (Purpose)",
@@ -1140,10 +1505,10 @@ async function provisionOpenSpec(ctx) {
1140
1505
  }
1141
1506
  }
1142
1507
  async function provisionOMC(ctx) {
1143
- await fs4.ensureDir(join3(ctx.cwd, ".pai"));
1144
- const omcPath = join3(ctx.cwd, ".pai", "omc.md");
1145
- if (!await fs4.pathExists(omcPath)) {
1146
- await fs4.writeFile(omcPath, [
1508
+ await fs5.ensureDir(join4(ctx.cwd, ".pai"));
1509
+ const omcPath = join4(ctx.cwd, ".pai", "omc.md");
1510
+ if (!await fs5.pathExists(omcPath)) {
1511
+ await fs5.writeFile(omcPath, [
1147
1512
  `# OMC \u2014 Object Model Context (${ctx.projectName})`,
1148
1513
  "",
1149
1514
  "> AI\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uB3C4\uBA54\uC778\uC744 \uC774\uD574\uD558\uAE30 \uC704\uD55C \uD575\uC2EC \uAC1D\uCCB4 \uBAA8\uB378",
@@ -1163,8 +1528,8 @@ async function provisionOMC(ctx) {
1163
1528
  }
1164
1529
  }
1165
1530
  async function provisionGstack(ctx) {
1166
- await fs4.ensureDir(join3(ctx.cwd, ".pai"));
1167
- await fs4.writeJson(join3(ctx.cwd, ".pai", "gstack.json"), {
1531
+ await fs5.ensureDir(join4(ctx.cwd, ".pai"));
1532
+ await fs5.writeJson(join4(ctx.cwd, ".pai", "gstack.json"), {
1168
1533
  version: "1.0",
1169
1534
  testRunner: "jest",
1170
1535
  coverageThreshold: { global: { lines: 80 } },
@@ -1172,10 +1537,10 @@ async function provisionGstack(ctx) {
1172
1537
  }, { spaces: 2 });
1173
1538
  }
1174
1539
  async function provisionRoboco(ctx) {
1175
- await fs4.ensureDir(join3(ctx.cwd, ".pai"));
1176
- const robocoPath = join3(ctx.cwd, ".pai", "roboco.json");
1177
- if (await fs4.pathExists(robocoPath)) return;
1178
- await fs4.writeJson(robocoPath, {
1540
+ await fs5.ensureDir(join4(ctx.cwd, ".pai"));
1541
+ const robocoPath = join4(ctx.cwd, ".pai", "roboco.json");
1542
+ if (await fs5.pathExists(robocoPath)) return;
1543
+ await fs5.writeJson(robocoPath, {
1179
1544
  version: "1.0",
1180
1545
  checks: ["github", "vercel", "supabase", "openspec", "omc"],
1181
1546
  reportOutput: "AI_READINESS_REPORT.md",
@@ -1183,14 +1548,18 @@ async function provisionRoboco(ctx) {
1183
1548
  }, { spaces: 2 });
1184
1549
  }
1185
1550
  async function provisionHarness(ctx) {
1186
- await fs4.ensureDir(join3(ctx.cwd, ".pai"));
1187
- await fs4.writeJson(join3(ctx.cwd, ".pai", "harness.json"), {
1551
+ await fs5.ensureDir(join4(ctx.cwd, ".pai"));
1552
+ await fs5.writeJson(join4(ctx.cwd, ".pai", "harness.json"), {
1188
1553
  version: "1.0",
1189
1554
  specFile: "docs/openspec.md",
1190
1555
  checkOn: ["pre-commit", "ci"],
1191
1556
  rules: ["spec-implementation-match", "api-contract-test"]
1192
1557
  }, { spaces: 2 });
1193
1558
  }
1559
+ async function provisionMcpWrapper(ctx) {
1560
+ const { provisionMcp: provisionMcp2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
1561
+ await provisionMcp2(ctx);
1562
+ }
1194
1563
  async function runProvisioners(keys, ctx) {
1195
1564
  const results = [];
1196
1565
  for (const key of keys) {
@@ -1208,7 +1577,7 @@ async function runProvisioners(keys, ctx) {
1208
1577
  }
1209
1578
  }
1210
1579
  if (Object.keys(ctx.envEntries).length > 0) {
1211
- writeEnvFile(join3(ctx.cwd, ".env.local"), ctx.envEntries);
1580
+ writeEnvFile(join4(ctx.cwd, ".env.local"), ctx.envEntries);
1212
1581
  }
1213
1582
  return results;
1214
1583
  }
@@ -1225,7 +1594,8 @@ var init_registry = __esm({
1225
1594
  omc: provisionOMC,
1226
1595
  gstack: provisionGstack,
1227
1596
  roboco: provisionRoboco,
1228
- harness: provisionHarness
1597
+ harness: provisionHarness,
1598
+ mcp: provisionMcpWrapper
1229
1599
  };
1230
1600
  }
1231
1601
  });
@@ -1236,21 +1606,21 @@ __export(claude_commands_exports, {
1236
1606
  provisionClaudeCommands: () => provisionClaudeCommands,
1237
1607
  upgradeClaudeCommands: () => upgradeClaudeCommands
1238
1608
  });
1239
- import { join as join4 } from "path";
1240
- import fs5 from "fs-extra";
1609
+ import { join as join5 } from "path";
1610
+ import fs6 from "fs-extra";
1241
1611
  async function writeCommandFiles(cmdDir, entries, options) {
1242
- await fs5.ensureDir(cmdDir);
1612
+ await fs6.ensureDir(cmdDir);
1243
1613
  const written = [];
1244
1614
  const skipped = [];
1245
1615
  const errors = [];
1246
1616
  for (const [filename, content] of Object.entries(entries)) {
1247
- const filePath = join4(cmdDir, filename);
1617
+ const filePath = join5(cmdDir, filename);
1248
1618
  try {
1249
- if (options.skipIfExists && await fs5.pathExists(filePath)) {
1619
+ if (options.skipIfExists && await fs6.pathExists(filePath)) {
1250
1620
  skipped.push(filename);
1251
1621
  continue;
1252
1622
  }
1253
- await fs5.writeFile(filePath, content);
1623
+ await fs6.writeFile(filePath, content);
1254
1624
  written.push(filename);
1255
1625
  } catch (err) {
1256
1626
  const msg = err instanceof Error ? err.message : String(err);
@@ -1260,13 +1630,13 @@ async function writeCommandFiles(cmdDir, entries, options) {
1260
1630
  return { written, skipped, errors };
1261
1631
  }
1262
1632
  async function provisionClaudeCommands(cwd) {
1263
- const skillDir = join4(cwd, ".claude", "skills", "pai");
1264
- await fs5.ensureDir(skillDir);
1265
- const skillPath = join4(skillDir, "SKILL.md");
1266
- if (!await fs5.pathExists(skillPath)) {
1267
- await fs5.writeFile(skillPath, SKILL_CONTENT);
1633
+ const skillDir = join5(cwd, ".claude", "skills", "pai");
1634
+ await fs6.ensureDir(skillDir);
1635
+ const skillPath = join5(skillDir, "SKILL.md");
1636
+ if (!await fs6.pathExists(skillPath)) {
1637
+ await fs6.writeFile(skillPath, SKILL_CONTENT);
1268
1638
  }
1269
- const cmdDir = join4(cwd, ".claude", "commands", "pai");
1639
+ const cmdDir = join5(cwd, ".claude", "commands", "pai");
1270
1640
  await writeCommandFiles(cmdDir, COMMANDS, { skipIfExists: true });
1271
1641
  }
1272
1642
  async function upgradeClaudeCommands(cwd) {
@@ -1275,16 +1645,16 @@ async function upgradeClaudeCommands(cwd) {
1275
1645
  commandsWritten: [],
1276
1646
  commandErrors: []
1277
1647
  };
1278
- const skillDir = join4(cwd, ".claude", "skills", "pai");
1279
- await fs5.ensureDir(skillDir);
1648
+ const skillDir = join5(cwd, ".claude", "skills", "pai");
1649
+ await fs6.ensureDir(skillDir);
1280
1650
  try {
1281
- await fs5.writeFile(join4(skillDir, "SKILL.md"), SKILL_CONTENT);
1651
+ await fs6.writeFile(join5(skillDir, "SKILL.md"), SKILL_CONTENT);
1282
1652
  result.skillUpdated = true;
1283
1653
  } catch (err) {
1284
1654
  const msg = err instanceof Error ? err.message : String(err);
1285
1655
  result.commandErrors.push({ file: "SKILL.md", error: msg });
1286
1656
  }
1287
- const cmdDir = join4(cwd, ".claude", "commands", "pai");
1657
+ const cmdDir = join5(cwd, ".claude", "commands", "pai");
1288
1658
  const { written, errors } = await writeCommandFiles(cmdDir, COMMANDS, { skipIfExists: false });
1289
1659
  result.commandsWritten = written;
1290
1660
  result.commandErrors.push(...errors);
@@ -1906,18 +2276,18 @@ npx pai-zero wakeup # \uC9C0\uAE08 \uB79C\uB364 \uBA54\uC2DC\uC9
1906
2276
  // src/core/config.ts
1907
2277
  import path from "path";
1908
2278
  import { createRequire } from "module";
1909
- import fs6 from "fs-extra";
2279
+ import fs7 from "fs-extra";
1910
2280
  async function loadConfig(cwd) {
1911
2281
  const configPath = path.join(cwd, CONFIG_DIR, CONFIG_FILE);
1912
- if (await fs6.pathExists(configPath)) {
1913
- return fs6.readJson(configPath);
2282
+ if (await fs7.pathExists(configPath)) {
2283
+ return fs7.readJson(configPath);
1914
2284
  }
1915
2285
  return null;
1916
2286
  }
1917
2287
  async function saveConfig(cwd, config) {
1918
2288
  const configDir = path.join(cwd, CONFIG_DIR);
1919
- await fs6.ensureDir(configDir);
1920
- await fs6.writeJson(path.join(configDir, CONFIG_FILE), config, { spaces: 2 });
2289
+ await fs7.ensureDir(configDir);
2290
+ await fs7.writeJson(path.join(configDir, CONFIG_FILE), config, { spaces: 2 });
1921
2291
  }
1922
2292
  function createDefaultConfig(projectName, mode) {
1923
2293
  return {
@@ -1944,9 +2314,9 @@ var doctor_exports = {};
1944
2314
  __export(doctor_exports, {
1945
2315
  runDoctor: () => runDoctor
1946
2316
  });
1947
- import { join as join5 } from "path";
2317
+ import { join as join6 } from "path";
1948
2318
  import { homedir } from "os";
1949
- import fs7 from "fs-extra";
2319
+ import fs8 from "fs-extra";
1950
2320
  async function runDoctor() {
1951
2321
  section("PAI Doctor \u2014 \uD658\uACBD \uC9C4\uB2E8");
1952
2322
  const checks = [];
@@ -1965,8 +2335,8 @@ async function runDoctor() {
1965
2335
  detail: claudeCheck.detail,
1966
2336
  fix: claudeCheck.ok ? void 0 : "npm install -g @anthropic-ai/claude-code"
1967
2337
  });
1968
- const globalConfigPath = join5(homedir(), ".pai", "config.json");
1969
- const hasGlobalConfig = await fs7.pathExists(globalConfigPath);
2338
+ const globalConfigPath = join6(homedir(), ".pai", "config.json");
2339
+ const hasGlobalConfig = await fs8.pathExists(globalConfigPath);
1970
2340
  checks.push({
1971
2341
  label: "\uAE00\uB85C\uBC8C \uC124\uC815",
1972
2342
  ok: true,
@@ -2088,7 +2458,8 @@ var init_environment = __esm({
2088
2458
  envEntries: {
2089
2459
  PAI_PROJECT_NAME: interview.projectName,
2090
2460
  PAI_MODE: interview.mode
2091
- }
2461
+ },
2462
+ mcp: interview.mcp
2092
2463
  };
2093
2464
  if (interview.authMethods.includes("custom")) {
2094
2465
  provCtx.envEntries["OAUTH_CLIENT_ID"] = interview.customAuth?.clientId || "YOUR_CLIENT_ID_HERE";
@@ -2108,6 +2479,7 @@ var init_environment = __esm({
2108
2479
  ...interview.extraTools.includes("supabase") ? ["supabase/config.toml"] : [],
2109
2480
  ...interview.extraTools.includes("gstack") ? [".pai/gstack.json"] : [],
2110
2481
  ...interview.extraTools.includes("harness") ? [".pai/harness.json"] : [],
2482
+ ...interview.extraTools.includes("mcp") ? ["mcp-server/", ".mcp.json"] : [],
2111
2483
  ".env.local"
2112
2484
  ];
2113
2485
  console.log("");
@@ -2175,7 +2547,7 @@ __export(detector_exports, {
2175
2547
  scanProjectState: () => scanProjectState
2176
2548
  });
2177
2549
  import path2 from "path";
2178
- import fs8 from "fs-extra";
2550
+ import fs9 from "fs-extra";
2179
2551
  async function scanProjectState(cwd) {
2180
2552
  const result = {
2181
2553
  isNewProject: true,
@@ -2186,10 +2558,10 @@ async function scanProjectState(cwd) {
2186
2558
  details: {}
2187
2559
  };
2188
2560
  const paiConfigPath = path2.join(cwd, ".pai", "config.json");
2189
- if (await fs8.pathExists(paiConfigPath)) {
2561
+ if (await fs9.pathExists(paiConfigPath)) {
2190
2562
  result.hasPaiConfig = true;
2191
2563
  try {
2192
- const config = await fs8.readJson(paiConfigPath);
2564
+ const config = await fs9.readJson(paiConfigPath);
2193
2565
  result.projectMode = config.mode ?? null;
2194
2566
  } catch {
2195
2567
  }
@@ -2197,7 +2569,7 @@ async function scanProjectState(cwd) {
2197
2569
  for (const [key, signatures] of Object.entries(PLUGIN_SIGNATURES)) {
2198
2570
  const installed = await Promise.any(
2199
2571
  signatures.map(async (sig) => {
2200
- if (await fs8.pathExists(path2.join(cwd, sig))) return true;
2572
+ if (await fs9.pathExists(path2.join(cwd, sig))) return true;
2201
2573
  throw new Error("not found");
2202
2574
  })
2203
2575
  ).catch(() => false);
@@ -2208,7 +2580,7 @@ async function scanProjectState(cwd) {
2208
2580
  result.missingPlugins.push(key);
2209
2581
  }
2210
2582
  }
2211
- const hasAnyContent = result.installedPlugins.length > 0 || await fs8.pathExists(path2.join(cwd, "package.json")) || await fs8.pathExists(path2.join(cwd, "src")) || await fs8.pathExists(path2.join(cwd, "README.md"));
2583
+ const hasAnyContent = result.installedPlugins.length > 0 || await fs9.pathExists(path2.join(cwd, "package.json")) || await fs9.pathExists(path2.join(cwd, "src")) || await fs9.pathExists(path2.join(cwd, "README.md"));
2212
2584
  result.isNewProject = !hasAnyContent;
2213
2585
  return result;
2214
2586
  }
@@ -2338,8 +2710,8 @@ var analyzer_exports2 = {};
2338
2710
  __export(analyzer_exports2, {
2339
2711
  analyzeRepository: () => analyzeRepository
2340
2712
  });
2341
- import { join as join6 } from "path";
2342
- import fs9 from "fs-extra";
2713
+ import { join as join7 } from "path";
2714
+ import fs10 from "fs-extra";
2343
2715
  async function analyzeRepository(repoPath) {
2344
2716
  try {
2345
2717
  return await aiAnalysis(repoPath);
@@ -2400,14 +2772,14 @@ async function checkTestCoverage(repoPath) {
2400
2772
  ".nycrc"
2401
2773
  ];
2402
2774
  for (const f of testConfigs) {
2403
- const found = await fs9.pathExists(join6(repoPath, f));
2775
+ const found = await fs10.pathExists(join7(repoPath, f));
2404
2776
  findings.push({ item: f, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2405
2777
  if (found) score += 20;
2406
2778
  }
2407
2779
  const testDirs = ["tests", "test", "__tests__", "spec"];
2408
2780
  let hasTestDir = false;
2409
2781
  for (const d of testDirs) {
2410
- if (await fs9.pathExists(join6(repoPath, d))) {
2782
+ if (await fs10.pathExists(join7(repoPath, d))) {
2411
2783
  findings.push({ item: d, found: true, details: "\uD14C\uC2A4\uD2B8 \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC" });
2412
2784
  hasTestDir = true;
2413
2785
  score += 30;
@@ -2431,7 +2803,7 @@ async function checkCiCd(repoPath) {
2431
2803
  { path: ".circleci", label: "CircleCI" }
2432
2804
  ];
2433
2805
  for (const { path: path3, label } of ciConfigs) {
2434
- const found = await fs9.pathExists(join6(repoPath, path3));
2806
+ const found = await fs10.pathExists(join7(repoPath, path3));
2435
2807
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2436
2808
  if (found) score += 40;
2437
2809
  }
@@ -2450,7 +2822,7 @@ async function checkHooks(repoPath) {
2450
2822
  { path: ".claude/settings.json", label: "Claude Code settings" }
2451
2823
  ];
2452
2824
  for (const { path: path3, label } of hookConfigs) {
2453
- const found = await fs9.pathExists(join6(repoPath, path3));
2825
+ const found = await fs10.pathExists(join7(repoPath, path3));
2454
2826
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2455
2827
  if (found) score += 20;
2456
2828
  }
@@ -2468,7 +2840,7 @@ async function checkRepoStructure(repoPath) {
2468
2840
  { path: ".gitignore", label: ".gitignore" }
2469
2841
  ];
2470
2842
  for (const { path: path3, label } of structureChecks) {
2471
- const found = await fs9.pathExists(join6(repoPath, path3));
2843
+ const found = await fs10.pathExists(join7(repoPath, path3));
2472
2844
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2473
2845
  if (found) score += 25;
2474
2846
  }
@@ -2485,7 +2857,7 @@ async function checkDocumentation(repoPath) {
2485
2857
  { path: "docs/openspec.md", label: "OpenSpec PRD", points: 25 }
2486
2858
  ];
2487
2859
  for (const { path: path3, label, points } of docChecks) {
2488
- const found = await fs9.pathExists(join6(repoPath, path3));
2860
+ const found = await fs10.pathExists(join7(repoPath, path3));
2489
2861
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2490
2862
  if (found) score += points;
2491
2863
  }
@@ -2504,7 +2876,7 @@ async function checkHarnessEngineering(repoPath) {
2504
2876
  { path: ".pai/config.json", label: "PAI config", points: 10 }
2505
2877
  ];
2506
2878
  for (const { path: path3, label, points } of harnessChecks) {
2507
- const found = await fs9.pathExists(join6(repoPath, path3));
2879
+ const found = await fs10.pathExists(join7(repoPath, path3));
2508
2880
  findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
2509
2881
  if (found) score += points;
2510
2882
  }
@@ -2887,56 +3259,56 @@ __export(shell_cd_exports, {
2887
3259
  installShellHelper: () => installShellHelper,
2888
3260
  requestCdAfter: () => requestCdAfter
2889
3261
  });
2890
- import { join as join7 } from "path";
3262
+ import { join as join8 } from "path";
2891
3263
  import { homedir as homedir2 } from "os";
2892
- import fs10 from "fs-extra";
3264
+ import fs11 from "fs-extra";
2893
3265
  async function requestCdAfter(targetDir) {
2894
- await fs10.ensureDir(PAI_DIR);
2895
- await fs10.writeFile(CD_FILE, targetDir);
3266
+ await fs11.ensureDir(PAI_DIR);
3267
+ await fs11.writeFile(CD_FILE, targetDir);
2896
3268
  }
2897
3269
  async function installShellHelper() {
2898
- await fs10.ensureDir(PAI_DIR);
3270
+ await fs11.ensureDir(PAI_DIR);
2899
3271
  if (isWindows) {
2900
3272
  return installPowerShellHelper();
2901
3273
  }
2902
3274
  return installBashHelper();
2903
3275
  }
2904
3276
  async function installBashHelper() {
2905
- await fs10.writeFile(HELPER_FILE_SH, BASH_HELPER);
3277
+ await fs11.writeFile(HELPER_FILE_SH, BASH_HELPER);
2906
3278
  const rcFile = getShellRcPath();
2907
3279
  const sourceLine = 'source "$HOME/.pai/shell-helper.sh"';
2908
- if (await fs10.pathExists(rcFile)) {
2909
- const content = await fs10.readFile(rcFile, "utf8");
3280
+ if (await fs11.pathExists(rcFile)) {
3281
+ const content = await fs11.readFile(rcFile, "utf8");
2910
3282
  if (content.includes("shell-helper.sh")) {
2911
3283
  return true;
2912
3284
  }
2913
- await fs10.appendFile(rcFile, `
3285
+ await fs11.appendFile(rcFile, `
2914
3286
  # PAI \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9
2915
3287
  ${sourceLine}
2916
3288
  `);
2917
3289
  return false;
2918
3290
  }
2919
- await fs10.writeFile(rcFile, `${sourceLine}
3291
+ await fs11.writeFile(rcFile, `${sourceLine}
2920
3292
  `);
2921
3293
  return false;
2922
3294
  }
2923
3295
  async function installPowerShellHelper() {
2924
- await fs10.writeFile(HELPER_FILE_PS1, POWERSHELL_HELPER);
3296
+ await fs11.writeFile(HELPER_FILE_PS1, POWERSHELL_HELPER);
2925
3297
  const rcFile = getShellRcPath();
2926
3298
  const sourceLine = '. "$env:USERPROFILE\\.pai\\shell-helper.ps1"';
2927
- await fs10.ensureDir(join7(rcFile, ".."));
2928
- if (await fs10.pathExists(rcFile)) {
2929
- const content = await fs10.readFile(rcFile, "utf8");
3299
+ await fs11.ensureDir(join8(rcFile, ".."));
3300
+ if (await fs11.pathExists(rcFile)) {
3301
+ const content = await fs11.readFile(rcFile, "utf8");
2930
3302
  if (content.includes("shell-helper.ps1")) {
2931
3303
  return true;
2932
3304
  }
2933
- await fs10.appendFile(rcFile, `
3305
+ await fs11.appendFile(rcFile, `
2934
3306
  # PAI \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9
2935
3307
  ${sourceLine}
2936
3308
  `);
2937
3309
  return false;
2938
3310
  }
2939
- await fs10.writeFile(rcFile, `${sourceLine}
3311
+ await fs11.writeFile(rcFile, `${sourceLine}
2940
3312
  `);
2941
3313
  return false;
2942
3314
  }
@@ -2945,10 +3317,10 @@ var init_shell_cd = __esm({
2945
3317
  "src/utils/shell-cd.ts"() {
2946
3318
  "use strict";
2947
3319
  init_platform();
2948
- PAI_DIR = join7(homedir2(), ".pai");
2949
- CD_FILE = join7(PAI_DIR, ".cd-after");
2950
- HELPER_FILE_SH = join7(PAI_DIR, "shell-helper.sh");
2951
- HELPER_FILE_PS1 = join7(PAI_DIR, "shell-helper.ps1");
3320
+ PAI_DIR = join8(homedir2(), ".pai");
3321
+ CD_FILE = join8(PAI_DIR, ".cd-after");
3322
+ HELPER_FILE_SH = join8(PAI_DIR, "shell-helper.sh");
3323
+ HELPER_FILE_PS1 = join8(PAI_DIR, "shell-helper.ps1");
2952
3324
  BASH_HELPER = `# PAI shell helper \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9 \uC9C0\uC6D0
2953
3325
  pai() {
2954
3326
  local cd_target="$HOME/.pai/.cd-after"
@@ -2990,12 +3362,12 @@ function pai {
2990
3362
 
2991
3363
  // src/stages/evaluation/cache.ts
2992
3364
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
2993
- import { join as join8 } from "path";
3365
+ import { join as join9 } from "path";
2994
3366
  import { createHash } from "crypto";
2995
3367
  function computeRepoHash(repoPath) {
2996
3368
  const hash = createHash("sha256");
2997
3369
  for (const file of FILES_TO_HASH) {
2998
- const fullPath = join8(repoPath, file);
3370
+ const fullPath = join9(repoPath, file);
2999
3371
  try {
3000
3372
  const content = readFileSync(fullPath);
3001
3373
  hash.update(`${file}:${content.length}`);
@@ -3006,7 +3378,7 @@ function computeRepoHash(repoPath) {
3006
3378
  return hash.digest("hex").slice(0, 16);
3007
3379
  }
3008
3380
  function getCachePath(repoPath) {
3009
- return join8(repoPath, CACHE_DIR, CACHE_FILE);
3381
+ return join9(repoPath, CACHE_DIR, CACHE_FILE);
3010
3382
  }
3011
3383
  function loadCache(repoPath) {
3012
3384
  try {
@@ -3017,7 +3389,7 @@ function loadCache(repoPath) {
3017
3389
  }
3018
3390
  }
3019
3391
  function saveCache(repoPath, store) {
3020
- const cacheDir = join8(repoPath, CACHE_DIR, "cache");
3392
+ const cacheDir = join9(repoPath, CACHE_DIR, "cache");
3021
3393
  if (!existsSync(cacheDir)) {
3022
3394
  mkdirSync(cacheDir, { recursive: true });
3023
3395
  }
@@ -3076,8 +3448,8 @@ var evaluate_cmd_exports = {};
3076
3448
  __export(evaluate_cmd_exports, {
3077
3449
  evaluateCommand: () => evaluateCommand
3078
3450
  });
3079
- import { join as join9, basename } from "path";
3080
- import fs11 from "fs-extra";
3451
+ import { join as join10, basename } from "path";
3452
+ import fs12 from "fs-extra";
3081
3453
  async function evaluateCommand(cwd, options) {
3082
3454
  const useCache = options.cache !== false;
3083
3455
  let llmOutput = useCache ? getCachedResult(cwd) : null;
@@ -3097,17 +3469,17 @@ async function evaluateCommand(cwd, options) {
3097
3469
  const config = await loadConfig(cwd);
3098
3470
  const projectName = config?.projectName ?? basename(cwd);
3099
3471
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3100
- const reportDir = join9(cwd, "docs", "p-reports");
3101
- const reportPath = join9(reportDir, `${today}.md`);
3102
- await fs11.ensureDir(reportDir);
3472
+ const reportDir = join10(cwd, "docs", "p-reports");
3473
+ const reportPath = join10(reportDir, `${today}.md`);
3474
+ await fs12.ensureDir(reportDir);
3103
3475
  const detailedReport = buildDetailedReport(result, projectName);
3104
- await fs11.writeFile(reportPath, detailedReport, "utf8");
3476
+ await fs12.writeFile(reportPath, detailedReport, "utf8");
3105
3477
  console.log("");
3106
3478
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
3107
3479
  console.log("");
3108
3480
  console.log(detailedReport);
3109
3481
  if (options.output) {
3110
- await fs11.writeFile(options.output, detailedReport, "utf8");
3482
+ await fs12.writeFile(options.output, detailedReport, "utf8");
3111
3483
  success(`\uCD94\uAC00 \uC800\uC7A5: ${options.output}`);
3112
3484
  }
3113
3485
  if (options.failUnder && result.totalScore < options.failUnder) {
@@ -3235,8 +3607,8 @@ var init_cmd_exports = {};
3235
3607
  __export(init_cmd_exports, {
3236
3608
  initCommand: () => initCommand
3237
3609
  });
3238
- import { join as join10, basename as basename2 } from "path";
3239
- import fs12 from "fs-extra";
3610
+ import { join as join11, basename as basename2 } from "path";
3611
+ import fs13 from "fs-extra";
3240
3612
  async function initCommand(cwd, nameArg) {
3241
3613
  printBanner();
3242
3614
  const { isWindows: isWindows2, diagnoseWindowsEnv: diagnoseWindowsEnv2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
@@ -3294,11 +3666,11 @@ async function initCommand(cwd, nameArg) {
3294
3666
  const evalResult = computeResult2(llmOutput);
3295
3667
  printReport2(evalResult);
3296
3668
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3297
- const reportDir = join10(cwd, "docs", "p-reports");
3298
- await fs12.ensureDir(reportDir);
3669
+ const reportDir = join11(cwd, "docs", "p-reports");
3670
+ await fs13.ensureDir(reportDir);
3299
3671
  const legacyName = basename2(cwd);
3300
3672
  const detailedReport = buildDetailedReport3(evalResult, legacyName);
3301
- await fs12.writeFile(join10(reportDir, `${today}.md`), detailedReport, "utf8");
3673
+ await fs13.writeFile(join11(reportDir, `${today}.md`), detailedReport, "utf8");
3302
3674
  console.log("");
3303
3675
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
3304
3676
  } catch {
@@ -3343,8 +3715,8 @@ async function initCommand(cwd, nameArg) {
3343
3715
  }]);
3344
3716
  projectName = answer.name.trim();
3345
3717
  }
3346
- const projectDir = join10(cwd, projectName);
3347
- if (await fs12.pathExists(projectDir)) {
3718
+ const projectDir = join11(cwd, projectName);
3719
+ if (await fs13.pathExists(projectDir)) {
3348
3720
  const existingConfig = await loadConfig(projectDir);
3349
3721
  if (existingConfig) {
3350
3722
  console.log("");
@@ -3366,7 +3738,7 @@ async function initCommand(cwd, nameArg) {
3366
3738
  return;
3367
3739
  }
3368
3740
  } else {
3369
- await fs12.ensureDir(projectDir);
3741
+ await fs13.ensureDir(projectDir);
3370
3742
  success(`${projectName}/ \uD3F4\uB354 \uC0DD\uC131`);
3371
3743
  }
3372
3744
  await setupInDirectory(projectDir, projectName);
@@ -3433,6 +3805,9 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
3433
3805
  if (extraTools.includes("supabase")) {
3434
3806
  console.log(` ${chalk8.cyan("Supabase")} DB + \uC778\uC99D + \uC2A4\uD1A0\uB9AC\uC9C0 (PostgreSQL \uAE30\uBC18)`);
3435
3807
  }
3808
+ if (extraTools.includes("mcp")) {
3809
+ console.log(` ${chalk8.cyan("MCP \uC11C\uBC84")} AI \uB3C4\uAD6C \u2014 Claude\uAC00 \uD638\uCD9C\uD560 \uCEE4\uC2A4\uD140 \uAE30\uB2A5/\uB370\uC774\uD130`);
3810
+ }
3436
3811
  console.log("");
3437
3812
  console.log(colors.accent(" \uC124\uCE58 \uD655\uC778"));
3438
3813
  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"));
@@ -3444,7 +3819,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
3444
3819
  { label: "\uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC", path: ".claude/skills/pai/SKILL.md" }
3445
3820
  ];
3446
3821
  for (const check of checks) {
3447
- const exists = await fs12.pathExists(join10(projectDir, check.path));
3822
+ const exists = await fs13.pathExists(join11(projectDir, check.path));
3448
3823
  console.log(` ${exists ? colors.success("\u2713") : colors.err("\u2717")} ${check.label.padEnd(16)} ${colors.dim(check.path)}`);
3449
3824
  }
3450
3825
  console.log("");
@@ -3469,10 +3844,10 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
3469
3844
  printReport2(evalResult);
3470
3845
  await sleep2(500);
3471
3846
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3472
- const reportDir = join10(projectDir, "docs", "p-reports");
3473
- await fs12.ensureDir(reportDir);
3847
+ const reportDir = join11(projectDir, "docs", "p-reports");
3848
+ await fs13.ensureDir(reportDir);
3474
3849
  const detailedReport = buildDetailedReport3(evalResult, projectName);
3475
- await fs12.writeFile(join10(reportDir, `${today}.md`), detailedReport, "utf8");
3850
+ await fs13.writeFile(join11(reportDir, `${today}.md`), detailedReport, "utf8");
3476
3851
  await sleep2(500);
3477
3852
  console.log("");
3478
3853
  success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
@@ -3500,7 +3875,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
3500
3875
  const shellRc = getShellRcPath2();
3501
3876
  let hasYoloAliasSet = false;
3502
3877
  try {
3503
- const rcContent = await fs12.readFile(shellRc, "utf8");
3878
+ const rcContent = await fs13.readFile(shellRc, "utf8");
3504
3879
  hasYoloAliasSet = checkYolo(rcContent);
3505
3880
  } catch {
3506
3881
  }
@@ -3521,10 +3896,10 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
3521
3896
  const { getYoloAliasLine: getYoloAliasLine2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
3522
3897
  const aliasLine = getYoloAliasLine2();
3523
3898
  try {
3524
- const rcContent = await fs12.readFile(shellRc, "utf8").catch(() => "");
3899
+ const rcContent = await fs13.readFile(shellRc, "utf8").catch(() => "");
3525
3900
  if (!rcContent.includes("claude-yolo")) {
3526
- await fs12.ensureDir(join10(shellRc, ".."));
3527
- await fs12.appendFile(shellRc, `
3901
+ await fs13.ensureDir(join11(shellRc, ".."));
3902
+ await fs13.appendFile(shellRc, `
3528
3903
  # PAI \u2014 claude-YOLO mode
3529
3904
  ${aliasLine}
3530
3905
  `);
@@ -3755,9 +4130,9 @@ async function installOrchestratorOnly(projectDir, projectName) {
3755
4130
  const evalResult = computeResult2(llmOutput);
3756
4131
  printReport2(evalResult);
3757
4132
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3758
- const reportDir = join10(projectDir, "docs", "p-reports");
3759
- await fs12.ensureDir(reportDir);
3760
- await fs12.writeFile(join10(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
4133
+ const reportDir = join11(projectDir, "docs", "p-reports");
4134
+ await fs13.ensureDir(reportDir);
4135
+ await fs13.writeFile(join11(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
3761
4136
  console.log("");
3762
4137
  hint(`\uC0C1\uC138 \uB9AC\uD3EC\uD2B8: docs/p-reports/${today}.md`);
3763
4138
  } catch {
@@ -3807,7 +4182,7 @@ async function detectLegacyProject(cwd) {
3807
4182
  ".gitignore"
3808
4183
  ];
3809
4184
  for (const signal of signals) {
3810
- if (await fs12.pathExists(join10(cwd, signal))) return true;
4185
+ if (await fs13.pathExists(join11(cwd, signal))) return true;
3811
4186
  }
3812
4187
  return false;
3813
4188
  }
@@ -3824,17 +4199,17 @@ var init_init_cmd = __esm({
3824
4199
  });
3825
4200
 
3826
4201
  // src/stages/design/openspec.ts
3827
- import { join as join11 } from "path";
3828
- import fs13 from "fs-extra";
4202
+ import { join as join12 } from "path";
4203
+ import fs14 from "fs-extra";
3829
4204
  async function initOpenSpec(cwd, projectName) {
3830
- const docsDir = join11(cwd, "docs");
3831
- await fs13.ensureDir(docsDir);
3832
- const openspecPath = join11(docsDir, "openspec.md");
3833
- if (await fs13.pathExists(openspecPath)) {
4205
+ const docsDir = join12(cwd, "docs");
4206
+ await fs14.ensureDir(docsDir);
4207
+ const openspecPath = join12(docsDir, "openspec.md");
4208
+ if (await fs14.pathExists(openspecPath)) {
3834
4209
  info("docs/openspec.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
3835
4210
  return;
3836
4211
  }
3837
- await fs13.writeFile(openspecPath, [
4212
+ await fs14.writeFile(openspecPath, [
3838
4213
  `# OpenSpec \u2014 ${projectName}`,
3839
4214
  "",
3840
4215
  "## 1. \uBAA9\uC801 (Purpose)",
@@ -3862,13 +4237,13 @@ async function initOpenSpec(cwd, projectName) {
3862
4237
  }
3863
4238
  async function validateOpenSpec(cwd) {
3864
4239
  const candidates = [
3865
- join11(cwd, "docs", "openspec.md"),
3866
- join11(cwd, "openspec.md"),
3867
- join11(cwd, ".pai", "openspec.md")
4240
+ join12(cwd, "docs", "openspec.md"),
4241
+ join12(cwd, "openspec.md"),
4242
+ join12(cwd, ".pai", "openspec.md")
3868
4243
  ];
3869
4244
  let specPath = null;
3870
4245
  for (const p of candidates) {
3871
- if (await fs13.pathExists(p)) {
4246
+ if (await fs14.pathExists(p)) {
3872
4247
  specPath = p;
3873
4248
  break;
3874
4249
  }
@@ -3882,7 +4257,7 @@ async function validateOpenSpec(cwd) {
3882
4257
  warnings: ["openspec.md \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `pai design init` \uC744 \uC2E4\uD589\uD558\uC138\uC694."]
3883
4258
  };
3884
4259
  }
3885
- const content = await fs13.readFile(specPath, "utf8");
4260
+ const content = await fs14.readFile(specPath, "utf8");
3886
4261
  const missing = [];
3887
4262
  let filled = 0;
3888
4263
  for (const section2 of REQUIRED_SECTIONS) {
@@ -3930,17 +4305,17 @@ var init_openspec = __esm({
3930
4305
  });
3931
4306
 
3932
4307
  // src/stages/design/omc.ts
3933
- import { join as join12 } from "path";
3934
- import fs14 from "fs-extra";
4308
+ import { join as join13 } from "path";
4309
+ import fs15 from "fs-extra";
3935
4310
  async function initOMC(cwd, projectName) {
3936
- const paiDir = join12(cwd, ".pai");
3937
- await fs14.ensureDir(paiDir);
3938
- const omcPath = join12(paiDir, "omc.md");
3939
- if (await fs14.pathExists(omcPath)) {
4311
+ const paiDir = join13(cwd, ".pai");
4312
+ await fs15.ensureDir(paiDir);
4313
+ const omcPath = join13(paiDir, "omc.md");
4314
+ if (await fs15.pathExists(omcPath)) {
3940
4315
  info(".pai/omc.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
3941
4316
  return;
3942
4317
  }
3943
- await fs14.writeFile(omcPath, [
4318
+ await fs15.writeFile(omcPath, [
3944
4319
  `# OMC \u2014 Object Model Context (${projectName})`,
3945
4320
  "",
3946
4321
  "> AI\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uB3C4\uBA54\uC778\uC744 \uC774\uD574\uD558\uAE30 \uC704\uD55C \uD575\uC2EC \uAC1D\uCCB4 \uBAA8\uB378",
@@ -4022,25 +4397,25 @@ var init_design_cmd = __esm({
4022
4397
  });
4023
4398
 
4024
4399
  // src/stages/validation/runner.ts
4025
- import { join as join13 } from "path";
4026
- import fs15 from "fs-extra";
4400
+ import { join as join14 } from "path";
4401
+ import fs16 from "fs-extra";
4027
4402
  async function runTests(cwd) {
4028
4403
  const start = Date.now();
4029
- const gstackPath = join13(cwd, ".pai", "gstack.json");
4404
+ const gstackPath = join14(cwd, ".pai", "gstack.json");
4030
4405
  let runner = "npm test";
4031
- if (await fs15.pathExists(gstackPath)) {
4406
+ if (await fs16.pathExists(gstackPath)) {
4032
4407
  try {
4033
- const config = await fs15.readJson(gstackPath);
4408
+ const config = await fs16.readJson(gstackPath);
4034
4409
  if (config.testRunner === "vitest") runner = "npx vitest run";
4035
4410
  else if (config.testRunner === "jest") runner = "npx jest";
4036
4411
  else if (config.testRunner === "mocha") runner = "npx mocha";
4037
4412
  } catch {
4038
4413
  }
4039
4414
  }
4040
- const pkgPath = join13(cwd, "package.json");
4041
- if (await fs15.pathExists(pkgPath)) {
4415
+ const pkgPath = join14(cwd, "package.json");
4416
+ if (await fs16.pathExists(pkgPath)) {
4042
4417
  try {
4043
- const pkg4 = await fs15.readJson(pkgPath);
4418
+ const pkg4 = await fs16.readJson(pkgPath);
4044
4419
  if (!pkg4.scripts?.test || pkg4.scripts.test.includes("no test specified")) {
4045
4420
  return {
4046
4421
  runner,
@@ -4082,16 +4457,16 @@ var init_runner = __esm({
4082
4457
  });
4083
4458
 
4084
4459
  // src/stages/validation/harness.ts
4085
- import { join as join14 } from "path";
4086
- import fs16 from "fs-extra";
4460
+ import { join as join15 } from "path";
4461
+ import fs17 from "fs-extra";
4087
4462
  async function runHarnessCheck(cwd) {
4088
- const harnessPath = join14(cwd, ".pai", "harness.json");
4089
- if (!await fs16.pathExists(harnessPath)) {
4463
+ const harnessPath = join15(cwd, ".pai", "harness.json");
4464
+ if (!await fs17.pathExists(harnessPath)) {
4090
4465
  return { enabled: false, specFile: null, rules: [], checks: [] };
4091
4466
  }
4092
4467
  let config;
4093
4468
  try {
4094
- config = await fs16.readJson(harnessPath);
4469
+ config = await fs17.readJson(harnessPath);
4095
4470
  } catch {
4096
4471
  return { enabled: false, specFile: null, rules: [], checks: [] };
4097
4472
  }
@@ -4099,8 +4474,8 @@ async function runHarnessCheck(cwd) {
4099
4474
  const rules = config.rules ?? [];
4100
4475
  const checks = [];
4101
4476
  if (rules.includes("spec-implementation-match")) {
4102
- const specExists = await fs16.pathExists(join14(cwd, specFile));
4103
- const srcExists = await fs16.pathExists(join14(cwd, "src"));
4477
+ const specExists = await fs17.pathExists(join15(cwd, specFile));
4478
+ const srcExists = await fs17.pathExists(join15(cwd, "src"));
4104
4479
  checks.push({
4105
4480
  rule: "spec-implementation-match",
4106
4481
  passed: specExists && srcExists,
@@ -4108,8 +4483,8 @@ async function runHarnessCheck(cwd) {
4108
4483
  });
4109
4484
  }
4110
4485
  if (rules.includes("api-contract-test")) {
4111
- const testDir = await fs16.pathExists(join14(cwd, "tests"));
4112
- const testDir2 = await fs16.pathExists(join14(cwd, "test"));
4486
+ const testDir = await fs17.pathExists(join15(cwd, "tests"));
4487
+ const testDir2 = await fs17.pathExists(join15(cwd, "test"));
4113
4488
  checks.push({
4114
4489
  rule: "api-contract-test",
4115
4490
  passed: testDir || testDir2,
@@ -4199,14 +4574,14 @@ var init_context = __esm({
4199
4574
  });
4200
4575
 
4201
4576
  // src/stages/design/index.ts
4202
- import { join as join15 } from "path";
4203
- import fs17 from "fs-extra";
4577
+ import { join as join16 } from "path";
4578
+ import fs18 from "fs-extra";
4204
4579
  async function autoInstallHarness(cwd) {
4205
- const harnessPath = join15(cwd, ".pai", "harness.json");
4206
- if (await fs17.pathExists(harnessPath)) return;
4580
+ const harnessPath = join16(cwd, ".pai", "harness.json");
4581
+ if (await fs18.pathExists(harnessPath)) return;
4207
4582
  await withSpinner("Harness Engineering \uC790\uB3D9 \uC124\uC815 \uC911...", async () => {
4208
- await fs17.ensureDir(join15(cwd, ".pai"));
4209
- await fs17.writeJson(harnessPath, {
4583
+ await fs18.ensureDir(join16(cwd, ".pai"));
4584
+ await fs18.writeJson(harnessPath, {
4210
4585
  version: "1.0",
4211
4586
  specFile: "docs/openspec.md",
4212
4587
  checkOn: ["pre-commit", "ci"],
@@ -4551,7 +4926,7 @@ __export(remove_cmd_exports, {
4551
4926
  removeCommand: () => removeCommand
4552
4927
  });
4553
4928
  import { basename as basename4, dirname } from "path";
4554
- import fs18 from "fs-extra";
4929
+ import fs19 from "fs-extra";
4555
4930
  async function removeCommand(cwd, options) {
4556
4931
  section("\uD504\uB85C\uC81D\uD2B8 \uC0AD\uC81C");
4557
4932
  const config = await loadConfig(cwd);
@@ -4569,7 +4944,7 @@ async function removeCommand(cwd, options) {
4569
4944
  console.log(colors.err(` ${folderName}/ \uD3F4\uB354 \uC804\uCCB4\uAC00 \uC0AD\uC81C\uB429\uB2C8\uB2E4.`));
4570
4945
  hint("\uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4571
4946
  console.log("");
4572
- const items = await fs18.readdir(cwd);
4947
+ const items = await fs19.readdir(cwd);
4573
4948
  const fileCount = items.filter((i) => !i.startsWith(".")).length;
4574
4949
  const hiddenCount = items.filter((i) => i.startsWith(".")).length;
4575
4950
  info(`\uD30C\uC77C/\uD3F4\uB354 ${fileCount}\uAC1C, \uC228\uAE40 \uD56D\uBAA9 ${hiddenCount}\uAC1C`);
@@ -4588,7 +4963,7 @@ async function removeCommand(cwd, options) {
4588
4963
  }
4589
4964
  process.chdir(parentDir);
4590
4965
  try {
4591
- await fs18.remove(cwd);
4966
+ await fs19.remove(cwd);
4592
4967
  console.log("");
4593
4968
  success(`${folderName}/ \uD504\uB85C\uC81D\uD2B8\uAC00 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
4594
4969
  try {
@@ -4741,8 +5116,8 @@ var savetoken_cmd_exports = {};
4741
5116
  __export(savetoken_cmd_exports, {
4742
5117
  savetokenCommand: () => savetokenCommand
4743
5118
  });
4744
- import { join as join16, relative } from "path";
4745
- import fs19 from "fs-extra";
5119
+ import { join as join17, relative } from "path";
5120
+ import fs20 from "fs-extra";
4746
5121
  import chalk6 from "chalk";
4747
5122
  async function savetokenCommand(cwd) {
4748
5123
  const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
@@ -4811,11 +5186,11 @@ async function savetokenCommand(cwd) {
4811
5186
  console.log(` \u2192 ${colors.dim("\uB8F0 \uAE30\uBC18 \uB85C\uC9C1, \uD0A4\uC6CC\uB4DC \uB9E4\uCE6D\uC73C\uB85C \uAC80\uD1A0 \uD6C4 \uB300\uCCB4")}`);
4812
5187
  console.log(` ${chalk6.red("\u25CF")} \uB192\uC74C \uCF54\uB4DC \uC0DD\uC131, \uBCF5\uC7A1\uD55C \uCD94\uB860, \uCC3D\uC758\uC801 \uC0DD\uC131`);
4813
5188
  console.log(` \u2192 ${colors.dim("AI \uD544\uC218 \u2014 \uD504\uB86C\uD504\uD2B8 \uCD5C\uC801\uD654\uB85C \uD1A0\uD070 \uC808\uAC10")}`);
4814
- const reportDir = join16(cwd, ".pai");
4815
- await fs19.ensureDir(reportDir);
5189
+ const reportDir = join17(cwd, ".pai");
5190
+ await fs20.ensureDir(reportDir);
4816
5191
  const report = buildReport(callSites, cwd);
4817
- const reportPath = join16(reportDir, "savetoken-report.md");
4818
- await fs19.writeFile(reportPath, report, "utf8");
5192
+ const reportPath = join17(reportDir, "savetoken-report.md");
5193
+ await fs20.writeFile(reportPath, report, "utf8");
4819
5194
  console.log("");
4820
5195
  success("\uC2A4\uCE94 \uB9AC\uD3EC\uD2B8 \uC800\uC7A5: .pai/savetoken-report.md");
4821
5196
  console.log("");
@@ -4971,9 +5346,9 @@ var wakeup_cmd_exports = {};
4971
5346
  __export(wakeup_cmd_exports, {
4972
5347
  wakeupCommand: () => wakeupCommand
4973
5348
  });
4974
- import { join as join17 } from "path";
5349
+ import { join as join18 } from "path";
4975
5350
  import { homedir as homedir3, platform as osPlatform } from "os";
4976
- import fs20 from "fs-extra";
5351
+ import fs21 from "fs-extra";
4977
5352
  import chalk7 from "chalk";
4978
5353
  async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
4979
5354
  if (timeOrAction === "off") {
@@ -5020,9 +5395,9 @@ async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
5020
5395
  projectDir,
5021
5396
  launchMode
5022
5397
  };
5023
- await fs20.ensureDir(PAI_DIR2);
5024
- await fs20.writeJson(CONFIG_FILE2, config, { spaces: 2 });
5025
- await fs20.writeJson(MESSAGES_FILE, MESSAGES);
5398
+ await fs21.ensureDir(PAI_DIR2);
5399
+ await fs21.writeJson(CONFIG_FILE2, config, { spaces: 2 });
5400
+ await fs21.writeJson(MESSAGES_FILE, MESSAGES);
5026
5401
  await createWakeupScript(config);
5027
5402
  if (osPlatform() === "darwin") {
5028
5403
  await setupMacOS(config);
@@ -5052,8 +5427,8 @@ async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
5052
5427
  async function setupMacOS(config) {
5053
5428
  const { execa } = await import("execa");
5054
5429
  const [hour, minute] = config.time.split(":").map(Number);
5055
- const plistDir = join17(homedir3(), "Library", "LaunchAgents");
5056
- await fs20.ensureDir(plistDir);
5430
+ const plistDir = join18(homedir3(), "Library", "LaunchAgents");
5431
+ await fs21.ensureDir(plistDir);
5057
5432
  const weekdays = scheduleToWeekdays(config.schedule);
5058
5433
  let calendarEntries;
5059
5434
  if (weekdays.length === 7) {
@@ -5089,7 +5464,7 @@ ${calendarEntries}
5089
5464
  <string>${PAI_DIR2}/wakeup.log</string>
5090
5465
  </dict>
5091
5466
  </plist>`;
5092
- await fs20.writeFile(PLIST_PATH, plist);
5467
+ await fs21.writeFile(PLIST_PATH, plist);
5093
5468
  await execa("launchctl", ["unload", PLIST_PATH]).catch(() => {
5094
5469
  });
5095
5470
  await execa("launchctl", ["load", PLIST_PATH]);
@@ -5111,9 +5486,9 @@ ${calendarEntries}
5111
5486
  async function setupWindows(config) {
5112
5487
  const { execa } = await import("execa");
5113
5488
  const [hour, minute] = config.time.split(":").map(Number);
5114
- const psScriptDir = join17(homedir3(), ".pai");
5115
- await fs20.ensureDir(psScriptDir);
5116
- const psScriptPath = join17(psScriptDir, "wakeup.ps1");
5489
+ const psScriptDir = join18(homedir3(), ".pai");
5490
+ await fs21.ensureDir(psScriptDir);
5491
+ const psScriptPath = join18(psScriptDir, "wakeup.ps1");
5117
5492
  const claudeCmd = config.launchMode === "yolo" ? "claude --dangerously-skip-permissions" : "claude";
5118
5493
  const psScript = `# PAI Wakeup \u2014 Claude Code \uC138\uC158 \uC790\uB3D9 \uC2DC\uC791
5119
5494
  $paiDir = "$env:USERPROFILE\\.pai"
@@ -5142,7 +5517,7 @@ $notifier.Show([Windows.UI.Notifications.ToastNotification]::new($xml))
5142
5517
  # Open PowerShell with Claude Code
5143
5518
  Start-Process powershell -ArgumentList "-NoExit", "-Command", "Get-Content '$todayFile'; Write-Host ''; Set-Location '${config.projectDir}'; ${claudeCmd}"
5144
5519
  `;
5145
- await fs20.writeFile(psScriptPath, psScript, "utf8");
5520
+ await fs21.writeFile(psScriptPath, psScript, "utf8");
5146
5521
  const daysMap = {
5147
5522
  "\uD3C9\uC77C": "MON,TUE,WED,THU,FRI",
5148
5523
  "\uB9E4\uC77C": "MON,TUE,WED,THU,FRI,SAT,SUN",
@@ -5190,7 +5565,7 @@ async function disableWakeup() {
5190
5565
  if (osPlatform() === "darwin") {
5191
5566
  await execa("launchctl", ["unload", PLIST_PATH]).catch(() => {
5192
5567
  });
5193
- await fs20.remove(PLIST_PATH).catch(() => {
5568
+ await fs21.remove(PLIST_PATH).catch(() => {
5194
5569
  });
5195
5570
  success("launchd \uC2A4\uCF00\uC904 \uC81C\uAC70");
5196
5571
  console.log("");
@@ -5211,14 +5586,14 @@ async function disableWakeup() {
5211
5586
  } else {
5212
5587
  await removeCronEntry();
5213
5588
  }
5214
- await fs20.remove(CONFIG_FILE2).catch(() => {
5589
+ await fs21.remove(CONFIG_FILE2).catch(() => {
5215
5590
  });
5216
5591
  console.log("");
5217
5592
  success("\u2600\uFE0F \uC6E8\uC774\uD06C\uC5C5 \uD574\uC81C \uC644\uB8CC");
5218
5593
  }
5219
5594
  async function showStatus() {
5220
- if (await fs20.pathExists(CONFIG_FILE2)) {
5221
- const config = await fs20.readJson(CONFIG_FILE2);
5595
+ if (await fs21.pathExists(CONFIG_FILE2)) {
5596
+ const config = await fs21.readJson(CONFIG_FILE2);
5222
5597
  console.log("");
5223
5598
  success("\u2600\uFE0F \uC6E8\uC774\uD06C\uC5C5 \uD65C\uC131\uD654");
5224
5599
  console.log(` \uC2DC\uAC04 ${chalk7.white(config.time)}`);
@@ -5226,7 +5601,7 @@ async function showStatus() {
5226
5601
  console.log(` \uD504\uB85C\uC81D\uD2B8 ${chalk7.white(config.projectDir)}`);
5227
5602
  console.log(` \uBAA8\uB4DC ${chalk7.white(config.launchMode === "yolo" ? "claude-YOLO mode" : "\uC77C\uBC18 \uBAA8\uB4DC")}`);
5228
5603
  if (osPlatform() === "darwin") {
5229
- const plistExists = await fs20.pathExists(PLIST_PATH);
5604
+ const plistExists = await fs21.pathExists(PLIST_PATH);
5230
5605
  console.log(` launchd ${plistExists ? chalk7.green("\uD65C\uC131") : chalk7.red("\uBE44\uD65C\uC131")}`);
5231
5606
  }
5232
5607
  console.log("");
@@ -5328,19 +5703,44 @@ try {
5328
5703
 
5329
5704
  echo "$MSG" > "$TODAY_FILE"
5330
5705
 
5331
- # macOS: Open Terminal + Claude Code
5706
+ # macOS: GUI \uC138\uC158 \uB300\uAE30 \uD6C4 Terminal \uC2E4\uD589
5332
5707
  if [[ "$(uname)" == "Darwin" ]]; then
5708
+ # Sleep\uC5D0\uC11C \uAE68\uC5B4\uB09C \uC9C1\uD6C4 GUI\uAC00 \uC544\uC9C1 \uC900\uBE44 \uC548 \uB428 \u2014 \uB85C\uADF8\uC778 \uD654\uBA74 \uC7A0\uAE08 \uD574\uC81C \uB300\uAE30
5709
+ MAX_WAIT=300 # \uCD5C\uB300 5\uBD84 \uB300\uAE30
5710
+ WAITED=0
5711
+ while [ $WAITED -lt $MAX_WAIT ]; do
5712
+ # WindowServer \uD504\uB85C\uC138\uC2A4\uAC00 \uC788\uACE0 \uCF58\uC194 \uC0AC\uC6A9\uC790\uAC00 \uC788\uC73C\uBA74 GUI \uC900\uBE44 \uC644\uB8CC
5713
+ if pgrep -q "WindowServer" && [ "$(stat -f%Su /dev/console 2>/dev/null)" = "$(whoami)" ]; then
5714
+ echo "[$(date)] GUI session ready (waited \${WAITED}s)" >> "$LOG_FILE"
5715
+ break
5716
+ fi
5717
+ sleep 5
5718
+ WAITED=$((WAITED + 5))
5719
+ done
5720
+
5721
+ if [ $WAITED -ge $MAX_WAIT ]; then
5722
+ echo "[$(date)] GUI session timeout \u2014 skipping Terminal launch" >> "$LOG_FILE"
5723
+ exit 0
5724
+ fi
5725
+
5726
+ # \uCD94\uAC00 \uC548\uC815\uD654 \uB300\uAE30
5727
+ sleep 10
5728
+
5333
5729
  # Notification
5334
5730
  FIRST_LINE=$(echo "$MSG" | head -1 | cut -c1-80)
5335
5731
  osascript -e "display notification \\"$FIRST_LINE\\" with title \\"\u2600\uFE0F PAI Wakeup\\" subtitle \\"Claude Code \uC138\uC158\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4\\"" 2>/dev/null
5336
5732
 
5337
- # Open Terminal.app with Claude Code
5338
- osascript <<'APPLESCRIPT'
5733
+ # Open Terminal.app with Claude Code (\uC7AC\uC2DC\uB3C4 3\uD68C)
5734
+ for attempt in 1 2 3; do
5735
+ osascript <<'APPLESCRIPT' 2>>"$LOG_FILE" && break
5339
5736
  tell application "Terminal"
5340
5737
  activate
5341
5738
  do script "clear; echo ''; cat '$TODAY_FILE'; echo ''; echo '\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'; echo ''; cd '${config.projectDir}' && ${claudeCmd}"
5342
5739
  end tell
5343
5740
  APPLESCRIPT
5741
+ echo "[$(date)] Terminal launch attempt $attempt failed, retrying..." >> "$LOG_FILE"
5742
+ sleep 10
5743
+ done
5344
5744
  fi
5345
5745
 
5346
5746
  # Linux: Open terminal + Claude Code
@@ -5354,7 +5754,7 @@ fi
5354
5754
 
5355
5755
  echo "[$(date)] PAI Wakeup completed" >> "$LOG_FILE"
5356
5756
  `;
5357
- await fs20.writeFile(SCRIPT_FILE, script, { mode: 493 });
5757
+ await fs21.writeFile(SCRIPT_FILE, script, { mode: 493 });
5358
5758
  }
5359
5759
  var PAI_DIR2, CONFIG_FILE2, MESSAGES_FILE, SCRIPT_FILE, PLIST_NAME, PLIST_PATH, CRON_MARKER, MESSAGES;
5360
5760
  var init_wakeup_cmd = __esm({
@@ -5362,12 +5762,12 @@ var init_wakeup_cmd = __esm({
5362
5762
  "use strict";
5363
5763
  init_ui();
5364
5764
  init_logger();
5365
- PAI_DIR2 = join17(homedir3(), ".pai");
5366
- CONFIG_FILE2 = join17(PAI_DIR2, "wakeup-config.json");
5367
- MESSAGES_FILE = join17(PAI_DIR2, "wakeup-messages.json");
5368
- SCRIPT_FILE = join17(PAI_DIR2, "wakeup.sh");
5765
+ PAI_DIR2 = join18(homedir3(), ".pai");
5766
+ CONFIG_FILE2 = join18(PAI_DIR2, "wakeup-config.json");
5767
+ MESSAGES_FILE = join18(PAI_DIR2, "wakeup-messages.json");
5768
+ SCRIPT_FILE = join18(PAI_DIR2, "wakeup.sh");
5369
5769
  PLIST_NAME = "com.pai.wakeup";
5370
- PLIST_PATH = join17(homedir3(), "Library", "LaunchAgents", `${PLIST_NAME}.plist`);
5770
+ PLIST_PATH = join18(homedir3(), "Library", "LaunchAgents", `${PLIST_NAME}.plist`);
5371
5771
  CRON_MARKER = "# PAI-WAKEUP";
5372
5772
  MESSAGES = [
5373
5773
  `Here's to the crazy ones. The misfits. The rebels. The troublemakers.