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 +1370 -573
- package/dist/bin/pai.js.map +1 -1
- package/dist/cli/index.js +1370 -573
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/pai.js
CHANGED
|
@@ -890,20 +890,22 @@ function generateClaudeMd(cwd, analysis, interview) {
|
|
|
890
890
|
|
|
891
891
|
## Project Overview
|
|
892
892
|
|
|
893
|
-
|
|
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: \
|
|
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 (\
|
|
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
|
|
906
|
-
5. **Evaluation** \u2014 \uD488\uC9C8 \uD3C9\uAC00
|
|
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
|
-
|
|
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**: \
|
|
919
|
-
-
|
|
920
|
-
- **\
|
|
921
|
-
- **\
|
|
922
|
-
- **Git \uCEE4\uBC0B \uADDC\uCE59**: \uC791\uC5C5 \uB2E8\uC704\uBCC4\uB85C \uC758\uBBF8 \uC788\uB294 \uCEE4\uBC0B \uBA54\uC2DC\uC9C0
|
|
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
|
|
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: ${
|
|
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
|
-
"
|
|
2080
|
-
"
|
|
2098
|
+
"<!-- roboco:start -->",
|
|
2099
|
+
"## \uD30C\uC774\uD504\uB77C\uC778 \uC9C4\uD589 \uC0C1\uD0DC (roboco \uC790\uB3D9 \uAD00\uB9AC)",
|
|
2081
2100
|
"",
|
|
2082
|
-
"
|
|
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
|
-
"
|
|
2088
|
-
`- [x]
|
|
2103
|
+
"### \uC644\uB8CC",
|
|
2104
|
+
`- [x] environment (pai init, ${now2.slice(0, 10)})`,
|
|
2105
|
+
"<!-- roboco:end -->",
|
|
2089
2106
|
"",
|
|
2090
|
-
"## \
|
|
2091
|
-
"
|
|
2092
|
-
"
|
|
2093
|
-
"
|
|
2094
|
-
"
|
|
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 (
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
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
|
-
"
|
|
2263
|
-
"
|
|
2264
|
-
"
|
|
2265
|
-
"
|
|
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
|
-
"
|
|
2268
|
-
"
|
|
2269
|
-
"
|
|
2270
|
-
"
|
|
2271
|
-
|
|
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
|
-
|
|
2279
|
-
|
|
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
|
|
2337
|
-
if (await fs7.pathExists(
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
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
|
|
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
|
|
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
|
|
3350
|
-
import
|
|
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
|
|
3382
|
-
await
|
|
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}`,
|
|
3741
|
+
await walk(`${currentSrc}/${relName}`, path5.join(currentDest, relName));
|
|
3407
3742
|
} else if (entry.type === "file") {
|
|
3408
|
-
const destFile =
|
|
3409
|
-
if (!overwrite && await
|
|
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
|
|
3442
|
-
import
|
|
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 =
|
|
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(` ${
|
|
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 =
|
|
3873
|
+
const envPath = path6.join(cwd, ".env.local");
|
|
3539
3874
|
let content = "";
|
|
3540
|
-
if (await
|
|
3541
|
-
content = await
|
|
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
|
|
3555
|
-
await
|
|
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 =
|
|
3559
|
-
await
|
|
3560
|
-
const skillPath =
|
|
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
|
|
3934
|
+
await fs12.writeFile(skillPath, body);
|
|
3600
3935
|
}
|
|
3601
3936
|
async function upsertClaudeMdBlock(cwd, installedKeys) {
|
|
3602
|
-
const claudeMdPath =
|
|
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
|
|
3623
|
-
content = await
|
|
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
|
|
3637
|
-
await
|
|
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:
|
|
4095
|
-
const found = await fs13.pathExists(join7(repoPath,
|
|
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:
|
|
4114
|
-
const found = await fs13.pathExists(join7(repoPath,
|
|
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:
|
|
4132
|
-
const found = await fs13.pathExists(join7(repoPath,
|
|
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:
|
|
4149
|
-
const found = await fs13.pathExists(join7(repoPath,
|
|
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:
|
|
4168
|
-
const found = await fs13.pathExists(join7(repoPath,
|
|
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
|
|
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: ${
|
|
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
|
|
5014
|
+
const now2 = Date.now();
|
|
4812
5015
|
for (const [key, entry] of Object.entries(store.entries)) {
|
|
4813
|
-
if (
|
|
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:
|
|
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
|
|
4821
|
-
|
|
4822
|
-
"src/stages/evaluation/cache.ts"() {
|
|
5657
|
+
var init_validate_cmd = __esm({
|
|
5658
|
+
"src/cli/commands/validate.cmd.ts"() {
|
|
4823
5659
|
"use strict";
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
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
|
|
4854
|
-
import
|
|
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
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
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
|
|
5057
|
-
import
|
|
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 =
|
|
5116
|
-
await
|
|
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
|
|
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 =
|
|
5165
|
-
if (await
|
|
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
|
|
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
|
|
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 =
|
|
5289
|
-
await
|
|
6190
|
+
const reportDir = join13(projectDir, "docs", "p-reports");
|
|
6191
|
+
await fs21.ensureDir(reportDir);
|
|
5290
6192
|
const detailedReport = buildDetailedReport3(evalResult, projectName);
|
|
5291
|
-
await
|
|
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
|
|
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
|
|
6264
|
+
const rcContent = await fs21.readFile(shellRc, "utf8").catch(() => "");
|
|
5363
6265
|
if (!rcContent.includes("claude-yolo")) {
|
|
5364
|
-
await
|
|
5365
|
-
await
|
|
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 =
|
|
5559
|
-
await
|
|
5560
|
-
await
|
|
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
|
|
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
|
|
5702
|
-
import
|
|
6603
|
+
import { join as join14 } from "path";
|
|
6604
|
+
import fs22 from "fs-extra";
|
|
5703
6605
|
async function initOpenSpec(cwd, projectName) {
|
|
5704
|
-
const docsDir =
|
|
5705
|
-
await
|
|
5706
|
-
const openspecPath =
|
|
5707
|
-
if (await
|
|
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
|
|
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
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
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
|
|
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
|
|
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
|
|
5808
|
-
import
|
|
6709
|
+
import { join as join15 } from "path";
|
|
6710
|
+
import fs23 from "fs-extra";
|
|
5809
6711
|
async function initOMC(cwd, projectName) {
|
|
5810
|
-
const paiDir =
|
|
5811
|
-
await
|
|
5812
|
-
const omcPath =
|
|
5813
|
-
if (await
|
|
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
|
|
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
|
-
|
|
5931
|
-
const { stdout, stderr } = await execa("npm", ["test"], {
|
|
6761
|
+
await withRoboco(
|
|
5932
6762
|
cwd,
|
|
5933
|
-
|
|
5934
|
-
|
|
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
|
-
|
|
5944
|
-
return {
|
|
5945
|
-
runner,
|
|
5946
|
-
passed: false,
|
|
5947
|
-
output,
|
|
5948
|
-
duration: Date.now() - start
|
|
5949
|
-
};
|
|
6779
|
+
handleRobocoError(err);
|
|
5950
6780
|
}
|
|
5951
6781
|
}
|
|
5952
|
-
|
|
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
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
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
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
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
|
|
6041
|
-
"src/cli/commands/
|
|
6814
|
+
var init_design_cmd = __esm({
|
|
6815
|
+
"src/cli/commands/design.cmd.ts"() {
|
|
6042
6816
|
"use strict";
|
|
6043
6817
|
init_ui();
|
|
6044
|
-
|
|
6045
|
-
|
|
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
|
|
6879
|
+
import fs24 from "fs-extra";
|
|
6102
6880
|
async function autoInstallHarness(cwd) {
|
|
6103
6881
|
const harnessPath = join16(cwd, ".pai", "harness.json");
|
|
6104
|
-
if (await
|
|
6882
|
+
if (await fs24.pathExists(harnessPath)) return;
|
|
6105
6883
|
await withSpinner("Harness Engineering \uC790\uB3D9 \uC124\uC815 \uC911...", async () => {
|
|
6106
|
-
await
|
|
6107
|
-
await
|
|
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
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7647
|
+
await fs26.ensureDir(reportDir);
|
|
6851
7648
|
const report = buildReport(callSites, cwd);
|
|
6852
7649
|
const reportPath = join17(reportDir, "savetoken-report.md");
|
|
6853
|
-
await
|
|
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
|
|
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
|
|
7059
|
-
await
|
|
7060
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7256
|
-
const config = await
|
|
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
|
|
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
|
|
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
|
});
|