pai-zero 0.12.1 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/pai.js +1137 -400
- package/dist/bin/pai.js.map +1 -1
- package/dist/cli/index.js +1137 -400
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/pai.js
CHANGED
|
@@ -54,6 +54,13 @@ var init_logger = __esm({
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
// src/core/config.ts
|
|
57
|
+
var config_exports = {};
|
|
58
|
+
__export(config_exports, {
|
|
59
|
+
createDefaultConfig: () => createDefaultConfig,
|
|
60
|
+
loadConfig: () => loadConfig,
|
|
61
|
+
normalizeMode: () => normalizeMode,
|
|
62
|
+
saveConfig: () => saveConfig
|
|
63
|
+
});
|
|
57
64
|
import path from "path";
|
|
58
65
|
import { createRequire } from "module";
|
|
59
66
|
import fs from "fs-extra";
|
|
@@ -2087,34 +2094,33 @@ async function provisionGitHub(ctx) {
|
|
|
2087
2094
|
await Promise.all(dirs.map((d) => fs7.ensureDir(join4(ctx.cwd, d))));
|
|
2088
2095
|
const handoffPath = join4(ctx.cwd, "handoff.md");
|
|
2089
2096
|
if (!await fs7.pathExists(handoffPath)) {
|
|
2090
|
-
const
|
|
2097
|
+
const now2 = (/* @__PURE__ */ new Date()).toLocaleString("ko-KR");
|
|
2091
2098
|
await fs7.writeFile(handoffPath, [
|
|
2092
2099
|
`# Handoff \u2014 ${ctx.projectName}`,
|
|
2093
2100
|
"",
|
|
2094
|
-
`> \uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8: ${
|
|
2101
|
+
`> \uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8: ${now2}`,
|
|
2102
|
+
`> **\uCC38\uACE0**: \`<!-- roboco:start -->\` \uBE14\uB85D\uC740 \uD30C\uC774\uD504\uB77C\uC778 \uC9C4\uD589 \uC2DC \uC790\uB3D9 \uAC31\uC2E0\uB429\uB2C8\uB2E4.`,
|
|
2103
|
+
`> \uADF8 \uC678 \uC139\uC158(\uB2E4\uC74C \uB2E8\uACC4, \uBA54\uBAA8)\uC740 \uC790\uC720\uB86D\uAC8C \uC218\uB3D9 \uD3B8\uC9D1 \uAC00\uB2A5.`,
|
|
2095
2104
|
"",
|
|
2096
|
-
"
|
|
2097
|
-
"
|
|
2105
|
+
"<!-- roboco:start -->",
|
|
2106
|
+
"## \uD30C\uC774\uD504\uB77C\uC778 \uC9C4\uD589 \uC0C1\uD0DC (roboco \uC790\uB3D9 \uAD00\uB9AC)",
|
|
2098
2107
|
"",
|
|
2099
|
-
"
|
|
2100
|
-
"- [ ] Ideation \uAE30\uBC18 PRD \uC791\uC131 \u2014 \uC2EC\uCE35 \uC778\uD130\uBDF0\uB85C \uB9E5\uB77D/\uAE30\uC220 \uC81C\uC57D \uBCF4\uAC15",
|
|
2101
|
-
"- [ ] \uD558\uB124\uC2A4 \uC5D4\uC9C0\uB2C8\uC5B4\uB9C1 \uAD6C\uC131 \uD655\uC778 \u2014 \uC5D0\uC774\uC804\uD2B8 \uC791\uC5C5 \uD658\uACBD \uAC80\uC99D",
|
|
2102
|
-
"- [ ] AI \uCF54\uB4DC \uC0DD\uC131 \uC2DC\uC791",
|
|
2108
|
+
"\uD604\uC7AC \uB2E8\uACC4: **environment**",
|
|
2103
2109
|
"",
|
|
2104
|
-
"
|
|
2105
|
-
`- [x]
|
|
2110
|
+
"### \uC644\uB8CC",
|
|
2111
|
+
`- [x] environment (pai init, ${now2.slice(0, 10)})`,
|
|
2112
|
+
"<!-- roboco:end -->",
|
|
2106
2113
|
"",
|
|
2107
|
-
"## \
|
|
2108
|
-
"
|
|
2109
|
-
"
|
|
2110
|
-
"
|
|
2111
|
-
"
|
|
2112
|
-
"4. AI \uCF54\uB4DC \uC0DD\uC131 \u2192 \uAC80\uC99D \u2192 \uD3C9\uAC00",
|
|
2113
|
-
"```",
|
|
2114
|
+
"## \uB2E4\uC74C \uB2E8\uACC4",
|
|
2115
|
+
"- [ ] `/pai design` \u2014 Ideation + PRD \uC2EC\uCE35 \uC778\uD130\uBDF0",
|
|
2116
|
+
"- [ ] Claude Code\uB85C \uCF54\uB4DC \uC0DD\uC131",
|
|
2117
|
+
"- [ ] `pai test` \u2014 \uD14C\uC2A4\uD2B8 + \uD558\uB124\uC2A4 \uAC80\uC99D",
|
|
2118
|
+
"- [ ] `pai grade` \u2014 AI \uC900\uBE44\uB3C4 6\uCE74\uD14C\uACE0\uB9AC \uD3C9\uAC00",
|
|
2114
2119
|
"",
|
|
2115
2120
|
"## \uBA54\uBAA8",
|
|
2116
2121
|
`- \uBAA8\uB4DC: ${ctx.mode}`,
|
|
2117
|
-
"- \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"
|
|
2122
|
+
"- \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",
|
|
2123
|
+
"- \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)"
|
|
2118
2124
|
].join("\n") + "\n");
|
|
2119
2125
|
}
|
|
2120
2126
|
const contribPath = join4(ctx.cwd, "CONTRIBUTING.md");
|
|
@@ -2485,13 +2491,20 @@ async function provisionGstack(ctx) {
|
|
|
2485
2491
|
}
|
|
2486
2492
|
async function provisionRoboco(ctx) {
|
|
2487
2493
|
await fs7.ensureDir(join4(ctx.cwd, ".pai"));
|
|
2488
|
-
const
|
|
2489
|
-
if (await fs7.pathExists(
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2494
|
+
const robocoPath2 = join4(ctx.cwd, ".pai", "roboco.json");
|
|
2495
|
+
if (await fs7.pathExists(robocoPath2)) {
|
|
2496
|
+
try {
|
|
2497
|
+
const existing = await fs7.readJson(robocoPath2);
|
|
2498
|
+
if (existing?.version === "2.0") return;
|
|
2499
|
+
} catch {
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
await fs7.writeJson(robocoPath2, {
|
|
2503
|
+
version: "2.0",
|
|
2504
|
+
currentStage: "environment",
|
|
2505
|
+
stageHistory: [],
|
|
2506
|
+
lock: null,
|
|
2507
|
+
gates: {}
|
|
2495
2508
|
}, { spaces: 2 });
|
|
2496
2509
|
}
|
|
2497
2510
|
async function provisionHarness(ctx) {
|
|
@@ -3592,6 +3605,540 @@ var init_detector = __esm({
|
|
|
3592
3605
|
}
|
|
3593
3606
|
});
|
|
3594
3607
|
|
|
3608
|
+
// src/core/roboco.ts
|
|
3609
|
+
import os2 from "os";
|
|
3610
|
+
import path5 from "path";
|
|
3611
|
+
import fs10 from "fs-extra";
|
|
3612
|
+
function robocoPath(cwd) {
|
|
3613
|
+
return path5.join(cwd, ROBOCO_PATH);
|
|
3614
|
+
}
|
|
3615
|
+
async function loadRobocoState(cwd) {
|
|
3616
|
+
const p = robocoPath(cwd);
|
|
3617
|
+
if (!await fs10.pathExists(p)) return freshState();
|
|
3618
|
+
try {
|
|
3619
|
+
const raw = await fs10.readJson(p);
|
|
3620
|
+
if (raw?.version !== "2.0") return freshState();
|
|
3621
|
+
return raw;
|
|
3622
|
+
} catch {
|
|
3623
|
+
return freshState();
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
async function saveRobocoState(cwd, state) {
|
|
3627
|
+
const p = robocoPath(cwd);
|
|
3628
|
+
await fs10.ensureDir(path5.dirname(p));
|
|
3629
|
+
await fs10.writeJson(p, state, { spaces: 2 });
|
|
3630
|
+
}
|
|
3631
|
+
function freshState() {
|
|
3632
|
+
return {
|
|
3633
|
+
version: "2.0",
|
|
3634
|
+
currentStage: "environment",
|
|
3635
|
+
stageHistory: [],
|
|
3636
|
+
lock: null,
|
|
3637
|
+
gates: {}
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3640
|
+
function isProcessAlive(pid) {
|
|
3641
|
+
try {
|
|
3642
|
+
process.kill(pid, 0);
|
|
3643
|
+
return true;
|
|
3644
|
+
} catch {
|
|
3645
|
+
return false;
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
function isLockStale(lock, now2 = /* @__PURE__ */ new Date()) {
|
|
3649
|
+
if (!isProcessAlive(lock.pid)) return true;
|
|
3650
|
+
if (new Date(lock.expiresAt).getTime() < now2.getTime()) return true;
|
|
3651
|
+
return false;
|
|
3652
|
+
}
|
|
3653
|
+
async function acquireLock(cwd, stage, command, ttlMs = DEFAULT_TTL_MS) {
|
|
3654
|
+
const state = await loadRobocoState(cwd);
|
|
3655
|
+
if (state.lock) {
|
|
3656
|
+
if (!isLockStale(state.lock)) {
|
|
3657
|
+
throw new RobocoLockError(formatLockError(state.lock), state.lock);
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
3661
|
+
const newLock = {
|
|
3662
|
+
pid: process.pid,
|
|
3663
|
+
stage,
|
|
3664
|
+
command,
|
|
3665
|
+
acquiredAt: now2.toISOString(),
|
|
3666
|
+
expiresAt: new Date(now2.getTime() + ttlMs).toISOString(),
|
|
3667
|
+
host: os2.hostname()
|
|
3668
|
+
};
|
|
3669
|
+
state.lock = newLock;
|
|
3670
|
+
await saveRobocoState(cwd, state);
|
|
3671
|
+
return state;
|
|
3672
|
+
}
|
|
3673
|
+
async function releaseLock(cwd) {
|
|
3674
|
+
const state = await loadRobocoState(cwd);
|
|
3675
|
+
if (state.lock && state.lock.pid === process.pid) {
|
|
3676
|
+
state.lock = null;
|
|
3677
|
+
await saveRobocoState(cwd, state);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
function formatLockError(lock) {
|
|
3681
|
+
const started = new Date(lock.acquiredAt);
|
|
3682
|
+
const mins = Math.round((Date.now() - started.getTime()) / 6e4);
|
|
3683
|
+
return [
|
|
3684
|
+
"\uB2E4\uB978 PAI \uBA85\uB839\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4",
|
|
3685
|
+
` stage: ${lock.stage}`,
|
|
3686
|
+
` command: ${lock.command}`,
|
|
3687
|
+
` \uC2DC\uC791: ${mins}\uBD84 \uC804 (PID ${lock.pid}${lock.host ? ", host " + lock.host : ""})`,
|
|
3688
|
+
" \uB300\uAE30\uD558\uC9C0 \uC54A\uACE0 \uC989\uC2DC \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uC644\uB8CC \uD6C4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694."
|
|
3689
|
+
].join("\n");
|
|
3690
|
+
}
|
|
3691
|
+
async function markStageStart(cwd, stage, command) {
|
|
3692
|
+
const state = await loadRobocoState(cwd);
|
|
3693
|
+
state.currentStage = stage;
|
|
3694
|
+
state.stageHistory.push({
|
|
3695
|
+
stage,
|
|
3696
|
+
status: "in_progress",
|
|
3697
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3698
|
+
by: command
|
|
3699
|
+
});
|
|
3700
|
+
await saveRobocoState(cwd, state);
|
|
3701
|
+
}
|
|
3702
|
+
async function markStageEnd(cwd, stage, success2, reason) {
|
|
3703
|
+
const state = await loadRobocoState(cwd);
|
|
3704
|
+
for (let i = state.stageHistory.length - 1; i >= 0; i--) {
|
|
3705
|
+
const entry = state.stageHistory[i];
|
|
3706
|
+
if (entry.stage === stage && entry.status === "in_progress") {
|
|
3707
|
+
entry.status = success2 ? "done" : "failed";
|
|
3708
|
+
entry.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3709
|
+
if (reason) entry.reason = reason;
|
|
3710
|
+
break;
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
await saveRobocoState(cwd, state);
|
|
3714
|
+
}
|
|
3715
|
+
async function saveGateResult(cwd, key, result) {
|
|
3716
|
+
const state = await loadRobocoState(cwd);
|
|
3717
|
+
state.gates[key] = result;
|
|
3718
|
+
await saveRobocoState(cwd, state);
|
|
3719
|
+
}
|
|
3720
|
+
async function withRoboco(cwd, stage, command, options, fn) {
|
|
3721
|
+
await acquireLock(cwd, stage, command, options.ttlMs);
|
|
3722
|
+
try {
|
|
3723
|
+
if (options.gate && !options.force) {
|
|
3724
|
+
const result = await options.gate(cwd);
|
|
3725
|
+
await saveGateResult(cwd, `${stage}.entry`, result);
|
|
3726
|
+
if (!result.passed && !options.skipGates) {
|
|
3727
|
+
await markStageEnd(cwd, stage, false, `\uAC8C\uC774\uD2B8 \uC2E4\uD328: ${result.name}`);
|
|
3728
|
+
throw new GateFailedError(result);
|
|
3729
|
+
}
|
|
3730
|
+
if (!result.passed && options.skipGates) {
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
await markStageStart(cwd, stage, command);
|
|
3734
|
+
const value = await fn();
|
|
3735
|
+
await markStageEnd(cwd, stage, true);
|
|
3736
|
+
return value;
|
|
3737
|
+
} catch (err) {
|
|
3738
|
+
if (!(err instanceof GateFailedError)) {
|
|
3739
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3740
|
+
await markStageEnd(cwd, stage, false, msg).catch(() => {
|
|
3741
|
+
});
|
|
3742
|
+
}
|
|
3743
|
+
throw err;
|
|
3744
|
+
} finally {
|
|
3745
|
+
await releaseLock(cwd).catch(() => {
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
async function syncHandoff(cwd) {
|
|
3750
|
+
const handoffPath = path5.join(cwd, "handoff.md");
|
|
3751
|
+
if (!await fs10.pathExists(handoffPath)) return;
|
|
3752
|
+
const state = await loadRobocoState(cwd);
|
|
3753
|
+
const block = buildHandoffBlock(state);
|
|
3754
|
+
const content = await fs10.readFile(handoffPath, "utf8");
|
|
3755
|
+
const startIdx = content.indexOf(HANDOFF_START);
|
|
3756
|
+
const endIdx = content.indexOf(HANDOFF_END);
|
|
3757
|
+
let next;
|
|
3758
|
+
if (startIdx >= 0 && endIdx > startIdx) {
|
|
3759
|
+
next = content.slice(0, startIdx) + block + content.slice(endIdx + HANDOFF_END.length);
|
|
3760
|
+
} else {
|
|
3761
|
+
const sep = content.endsWith("\n") ? "\n" : "\n\n";
|
|
3762
|
+
next = content + sep + block + "\n";
|
|
3763
|
+
}
|
|
3764
|
+
await fs10.writeFile(handoffPath, next);
|
|
3765
|
+
}
|
|
3766
|
+
function buildHandoffBlock(state) {
|
|
3767
|
+
const lines = [HANDOFF_START, "## \uD30C\uC774\uD504\uB77C\uC778 \uC9C4\uD589 \uC0C1\uD0DC (roboco \uC790\uB3D9 \uAD00\uB9AC)", ""];
|
|
3768
|
+
const done = state.stageHistory.filter((h) => h.status === "done");
|
|
3769
|
+
const inProgress = state.stageHistory.filter((h) => h.status === "in_progress");
|
|
3770
|
+
const failed = state.stageHistory.filter((h) => h.status === "failed");
|
|
3771
|
+
if (inProgress.length > 0) {
|
|
3772
|
+
lines.push("### \uC9C4\uD589 \uC911");
|
|
3773
|
+
for (const h of inProgress) {
|
|
3774
|
+
lines.push(`- [ ] ${h.stage} (${h.by}, \uC2DC\uC791 ${fmtDate(h.startedAt)})`);
|
|
3775
|
+
}
|
|
3776
|
+
lines.push("");
|
|
3777
|
+
}
|
|
3778
|
+
if (failed.length > 0) {
|
|
3779
|
+
lines.push("### \uC2E4\uD328");
|
|
3780
|
+
for (const h of failed) {
|
|
3781
|
+
lines.push(`- [x] ${h.stage} \u2014 ${h.reason ?? "\uC6D0\uC778 \uBD88\uBA85"} (${fmtDate(h.startedAt)})`);
|
|
3782
|
+
}
|
|
3783
|
+
lines.push("");
|
|
3784
|
+
}
|
|
3785
|
+
if (done.length > 0) {
|
|
3786
|
+
lines.push("### \uC644\uB8CC");
|
|
3787
|
+
for (const h of done) {
|
|
3788
|
+
lines.push(`- [x] ${h.stage} (${h.by}, ${fmtDate(h.endedAt ?? h.startedAt)})`);
|
|
3789
|
+
}
|
|
3790
|
+
lines.push("");
|
|
3791
|
+
}
|
|
3792
|
+
lines.push(`\uD604\uC7AC \uB2E8\uACC4: **${state.currentStage}**`);
|
|
3793
|
+
lines.push(HANDOFF_END);
|
|
3794
|
+
return lines.join("\n");
|
|
3795
|
+
}
|
|
3796
|
+
function fmtDate(iso) {
|
|
3797
|
+
try {
|
|
3798
|
+
const d = new Date(iso);
|
|
3799
|
+
return d.toISOString().slice(0, 16).replace("T", " ");
|
|
3800
|
+
} catch {
|
|
3801
|
+
return iso;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
var ROBOCO_PATH, DEFAULT_TTL_MS, RobocoLockError, GateFailedError, HANDOFF_START, HANDOFF_END;
|
|
3805
|
+
var init_roboco = __esm({
|
|
3806
|
+
"src/core/roboco.ts"() {
|
|
3807
|
+
"use strict";
|
|
3808
|
+
ROBOCO_PATH = path5.join(".pai", "roboco.json");
|
|
3809
|
+
DEFAULT_TTL_MS = 10 * 60 * 1e3;
|
|
3810
|
+
RobocoLockError = class extends Error {
|
|
3811
|
+
constructor(message, holder) {
|
|
3812
|
+
super(message);
|
|
3813
|
+
this.holder = holder;
|
|
3814
|
+
this.name = "RobocoLockError";
|
|
3815
|
+
}
|
|
3816
|
+
holder;
|
|
3817
|
+
};
|
|
3818
|
+
GateFailedError = class extends Error {
|
|
3819
|
+
constructor(result) {
|
|
3820
|
+
super(`\uAC8C\uC774\uD2B8 \uC2E4\uD328 (${result.name}): ${result.violations.length}\uAC74 \uC704\uBC18`);
|
|
3821
|
+
this.result = result;
|
|
3822
|
+
this.name = "GateFailedError";
|
|
3823
|
+
}
|
|
3824
|
+
result;
|
|
3825
|
+
};
|
|
3826
|
+
HANDOFF_START = "<!-- roboco:start -->";
|
|
3827
|
+
HANDOFF_END = "<!-- roboco:end -->";
|
|
3828
|
+
}
|
|
3829
|
+
});
|
|
3830
|
+
|
|
3831
|
+
// src/core/gates.ts
|
|
3832
|
+
var gates_exports = {};
|
|
3833
|
+
__export(gates_exports, {
|
|
3834
|
+
STAGE_GATES: () => STAGE_GATES,
|
|
3835
|
+
countBusinessRules: () => countBusinessRules,
|
|
3836
|
+
countDomainObjects: () => countDomainObjects,
|
|
3837
|
+
countEndpoints: () => countEndpoints,
|
|
3838
|
+
designEntryGate: () => designEntryGate,
|
|
3839
|
+
evaluationEntryGate: () => evaluationEntryGate,
|
|
3840
|
+
executionEntryGate: () => executionEntryGate,
|
|
3841
|
+
validateEdges: () => validateEdges,
|
|
3842
|
+
validationEntryGate: () => validationEntryGate
|
|
3843
|
+
});
|
|
3844
|
+
import path6 from "path";
|
|
3845
|
+
import fs11 from "fs-extra";
|
|
3846
|
+
function now() {
|
|
3847
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3848
|
+
}
|
|
3849
|
+
function makeResult(name, violations) {
|
|
3850
|
+
return {
|
|
3851
|
+
name,
|
|
3852
|
+
passed: violations.length === 0,
|
|
3853
|
+
checkedAt: now(),
|
|
3854
|
+
violations
|
|
3855
|
+
};
|
|
3856
|
+
}
|
|
3857
|
+
async function designEntryGate(cwd) {
|
|
3858
|
+
const violations = [];
|
|
3859
|
+
const claudeMd = path6.join(cwd, "CLAUDE.md");
|
|
3860
|
+
if (!await fs11.pathExists(claudeMd)) {
|
|
3861
|
+
violations.push({
|
|
3862
|
+
rule: "claude-md-exists",
|
|
3863
|
+
message: "CLAUDE.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 `pai init`\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
3864
|
+
location: "CLAUDE.md"
|
|
3865
|
+
});
|
|
3866
|
+
}
|
|
3867
|
+
const configJson = path6.join(cwd, ".pai", "config.json");
|
|
3868
|
+
if (!await fs11.pathExists(configJson)) {
|
|
3869
|
+
violations.push({
|
|
3870
|
+
rule: "pai-config-exists",
|
|
3871
|
+
message: "PAI \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `pai init`\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
3872
|
+
location: ".pai/config.json"
|
|
3873
|
+
});
|
|
3874
|
+
}
|
|
3875
|
+
return makeResult("design.entry", violations);
|
|
3876
|
+
}
|
|
3877
|
+
async function executionEntryGate(cwd) {
|
|
3878
|
+
const violations = [];
|
|
3879
|
+
const openspec = path6.join(cwd, "docs", "openspec.md");
|
|
3880
|
+
const hasOpenspec = await fs11.pathExists(openspec);
|
|
3881
|
+
if (!hasOpenspec) {
|
|
3882
|
+
violations.push({
|
|
3883
|
+
rule: "openspec-exists",
|
|
3884
|
+
message: "docs/openspec.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
3885
|
+
location: "docs/openspec.md"
|
|
3886
|
+
});
|
|
3887
|
+
} else {
|
|
3888
|
+
const content = await fs11.readFile(openspec, "utf8");
|
|
3889
|
+
const endpointCount = countEndpoints(content);
|
|
3890
|
+
if (endpointCount < 1) {
|
|
3891
|
+
violations.push({
|
|
3892
|
+
rule: "openspec-endpoints-min",
|
|
3893
|
+
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. /pai design \uC2E4\uD589 \uAD8C\uC7A5.`,
|
|
3894
|
+
location: "docs/openspec.md \xA7 5. API \uC5D4\uB4DC\uD3EC\uC778\uD2B8"
|
|
3895
|
+
});
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
const omc = path6.join(cwd, ".pai", "omc.md");
|
|
3899
|
+
if (await fs11.pathExists(omc)) {
|
|
3900
|
+
const content = await fs11.readFile(omc, "utf8");
|
|
3901
|
+
const domainCount = countDomainObjects(content);
|
|
3902
|
+
if (domainCount < 1) {
|
|
3903
|
+
violations.push({
|
|
3904
|
+
rule: "omc-domain-min",
|
|
3905
|
+
message: "omc.md\uC5D0 \uB3C4\uBA54\uC778 \uAC1D\uCCB4\uAC00 0\uAC1C\uC785\uB2C8\uB2E4. \uCD5C\uC18C 1\uAC1C \uD544\uC694.",
|
|
3906
|
+
location: ".pai/omc.md \xA7 \uB3C4\uBA54\uC778 \uAC1D\uCCB4"
|
|
3907
|
+
});
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
const edgeViolations = await validateEdges(cwd);
|
|
3911
|
+
for (const ev of edgeViolations) {
|
|
3912
|
+
violations.push({
|
|
3913
|
+
rule: `edge-${ev.edge.from}-${ev.edge.to}`,
|
|
3914
|
+
message: ev.message,
|
|
3915
|
+
location: ev.edge.path
|
|
3916
|
+
});
|
|
3917
|
+
}
|
|
3918
|
+
return makeResult("execution.entry", violations);
|
|
3919
|
+
}
|
|
3920
|
+
async function validationEntryGate(cwd) {
|
|
3921
|
+
const violations = [];
|
|
3922
|
+
const srcDir = path6.join(cwd, "src");
|
|
3923
|
+
if (!await fs11.pathExists(srcDir)) {
|
|
3924
|
+
violations.push({
|
|
3925
|
+
rule: "src-dir-exists",
|
|
3926
|
+
message: "src/ \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
3927
|
+
location: "src/"
|
|
3928
|
+
});
|
|
3929
|
+
} else {
|
|
3930
|
+
const codeFileCount = await countCodeFiles(srcDir);
|
|
3931
|
+
if (codeFileCount === 0) {
|
|
3932
|
+
violations.push({
|
|
3933
|
+
rule: "code-files-min",
|
|
3934
|
+
message: "src/ \uB0B4\uBD80\uC5D0 \uCF54\uB4DC \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uCD5C\uC18C 1\uAC1C \uD544\uC694.",
|
|
3935
|
+
location: "src/"
|
|
3936
|
+
});
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
const edgeViolations = await validateEdges(cwd);
|
|
3940
|
+
for (const ev of edgeViolations) {
|
|
3941
|
+
violations.push({
|
|
3942
|
+
rule: `edge-${ev.edge.from}-${ev.edge.to}`,
|
|
3943
|
+
message: ev.message,
|
|
3944
|
+
location: ev.edge.path
|
|
3945
|
+
});
|
|
3946
|
+
}
|
|
3947
|
+
return makeResult("validation.entry", violations);
|
|
3948
|
+
}
|
|
3949
|
+
async function evaluationEntryGate(cwd) {
|
|
3950
|
+
const violations = [];
|
|
3951
|
+
const state = await loadRobocoState(cwd);
|
|
3952
|
+
const recentValidation = [...state.stageHistory].reverse().find((h) => h.stage === "validation");
|
|
3953
|
+
if (!recentValidation) {
|
|
3954
|
+
violations.push({
|
|
3955
|
+
rule: "validation-executed",
|
|
3956
|
+
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."
|
|
3957
|
+
});
|
|
3958
|
+
} else if (recentValidation.status !== "done") {
|
|
3959
|
+
violations.push({
|
|
3960
|
+
rule: "validation-passed",
|
|
3961
|
+
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.`
|
|
3962
|
+
});
|
|
3963
|
+
} else {
|
|
3964
|
+
const endedAt = recentValidation.endedAt ?? recentValidation.startedAt;
|
|
3965
|
+
const age = Date.now() - new Date(endedAt).getTime();
|
|
3966
|
+
if (age > EVAL_GATE_WINDOW_MS) {
|
|
3967
|
+
const mins = Math.round(age / 6e4);
|
|
3968
|
+
violations.push({
|
|
3969
|
+
rule: "validation-recent",
|
|
3970
|
+
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.`
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
return makeResult("evaluation.entry", violations);
|
|
3975
|
+
}
|
|
3976
|
+
function countEndpoints(openspecContent) {
|
|
3977
|
+
const lines = openspecContent.split("\n");
|
|
3978
|
+
let inTable = false;
|
|
3979
|
+
let count = 0;
|
|
3980
|
+
for (const rawLine of lines) {
|
|
3981
|
+
const line = rawLine.trim();
|
|
3982
|
+
if (/^##\s*5\.|^##\s*API|API 엔드포인트/i.test(line)) {
|
|
3983
|
+
inTable = true;
|
|
3984
|
+
continue;
|
|
3985
|
+
}
|
|
3986
|
+
if (!inTable) continue;
|
|
3987
|
+
if (/^##\s/.test(line) && !/5\.|API/i.test(line)) {
|
|
3988
|
+
inTable = false;
|
|
3989
|
+
continue;
|
|
3990
|
+
}
|
|
3991
|
+
if (/^\|\s*Method/i.test(line)) continue;
|
|
3992
|
+
if (/^\|\s*[-:]+\s*\|/.test(line)) continue;
|
|
3993
|
+
const m = line.match(/^\|\s*(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*\|\s*(\/\S+)\s*\|/i);
|
|
3994
|
+
if (m) count++;
|
|
3995
|
+
}
|
|
3996
|
+
return count;
|
|
3997
|
+
}
|
|
3998
|
+
function countDomainObjects(omcContent) {
|
|
3999
|
+
const lines = omcContent.split("\n");
|
|
4000
|
+
let count = 0;
|
|
4001
|
+
let inDomainSection = false;
|
|
4002
|
+
for (const rawLine of lines) {
|
|
4003
|
+
const line = rawLine.trim();
|
|
4004
|
+
if (/^##\s*도메인 객체|^##\s*Domain Objects/i.test(line)) {
|
|
4005
|
+
inDomainSection = true;
|
|
4006
|
+
continue;
|
|
4007
|
+
}
|
|
4008
|
+
if (!inDomainSection) continue;
|
|
4009
|
+
if (/^##\s/.test(line) && !/도메인|Domain/i.test(line)) {
|
|
4010
|
+
inDomainSection = false;
|
|
4011
|
+
continue;
|
|
4012
|
+
}
|
|
4013
|
+
if (/^###\s+\w/.test(line)) count++;
|
|
4014
|
+
}
|
|
4015
|
+
return count;
|
|
4016
|
+
}
|
|
4017
|
+
async function validateEdges(cwd) {
|
|
4018
|
+
const config = await loadConfig(cwd);
|
|
4019
|
+
if (!config?.edges || config.edges.length === 0) return [];
|
|
4020
|
+
const violations = [];
|
|
4021
|
+
for (const edge of config.edges) {
|
|
4022
|
+
if (edge.path) {
|
|
4023
|
+
const fullPath = path6.join(cwd, edge.path);
|
|
4024
|
+
if (!await fs11.pathExists(fullPath)) {
|
|
4025
|
+
violations.push({
|
|
4026
|
+
edge,
|
|
4027
|
+
message: `${edge.from}\u2192${edge.to} \uACC4\uC57D \uD30C\uC77C \uC5C6\uC74C: ${edge.path}`
|
|
4028
|
+
});
|
|
4029
|
+
continue;
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
switch (edge.via) {
|
|
4033
|
+
case "specFile":
|
|
4034
|
+
case "endpoints": {
|
|
4035
|
+
const specPath = edge.path ? path6.join(cwd, edge.path) : path6.join(cwd, "docs", "openspec.md");
|
|
4036
|
+
if (await fs11.pathExists(specPath)) {
|
|
4037
|
+
const content = await fs11.readFile(specPath, "utf8");
|
|
4038
|
+
const count = countEndpoints(content);
|
|
4039
|
+
if (count === 0) {
|
|
4040
|
+
violations.push({
|
|
4041
|
+
edge,
|
|
4042
|
+
message: `${edge.from}\u2192${edge.to}: openspec.md\uC5D0 \uC815\uC758\uB41C \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uAC00 0\uAC1C (\uCD5C\uC18C 1\uAC1C \uD544\uC694)`
|
|
4043
|
+
});
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
break;
|
|
4047
|
+
}
|
|
4048
|
+
case "domain": {
|
|
4049
|
+
const omcPath = edge.path ? path6.join(cwd, edge.path) : path6.join(cwd, ".pai", "omc.md");
|
|
4050
|
+
if (await fs11.pathExists(omcPath)) {
|
|
4051
|
+
const content = await fs11.readFile(omcPath, "utf8");
|
|
4052
|
+
const count = countDomainObjects(content);
|
|
4053
|
+
if (count === 0) {
|
|
4054
|
+
violations.push({
|
|
4055
|
+
edge,
|
|
4056
|
+
message: `${edge.from}\u2192${edge.to}: omc.md\uC5D0 \uB3C4\uBA54\uC778 \uAC1D\uCCB4\uAC00 0\uAC1C (\uCD5C\uC18C 1\uAC1C \uD544\uC694)`
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
break;
|
|
4061
|
+
}
|
|
4062
|
+
case "businessRules": {
|
|
4063
|
+
const omcPath = edge.path ? path6.join(cwd, edge.path) : path6.join(cwd, ".pai", "omc.md");
|
|
4064
|
+
if (await fs11.pathExists(omcPath)) {
|
|
4065
|
+
const content = await fs11.readFile(omcPath, "utf8");
|
|
4066
|
+
const ruleCount = countBusinessRules(content);
|
|
4067
|
+
if (ruleCount === 0) {
|
|
4068
|
+
violations.push({
|
|
4069
|
+
edge,
|
|
4070
|
+
message: `${edge.from}\u2192${edge.to}: omc.md\uC5D0 \uBE44\uC988\uB2C8\uC2A4 \uADDC\uCE59\uC774 0\uAC1C \u2014 harness \uAC80\uC99D \uB300\uC0C1 \uC5C6\uC74C`
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
break;
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
return violations;
|
|
4079
|
+
}
|
|
4080
|
+
function countBusinessRules(omcContent) {
|
|
4081
|
+
const lines = omcContent.split("\n");
|
|
4082
|
+
let inSection = false;
|
|
4083
|
+
let count = 0;
|
|
4084
|
+
for (const rawLine of lines) {
|
|
4085
|
+
const line = rawLine.trim();
|
|
4086
|
+
if (/^##\s*비즈니스 규칙|^##\s*Business Rules/i.test(line)) {
|
|
4087
|
+
inSection = true;
|
|
4088
|
+
continue;
|
|
4089
|
+
}
|
|
4090
|
+
if (!inSection) continue;
|
|
4091
|
+
if (/^##\s/.test(line) && !/비즈니스|Business/i.test(line)) {
|
|
4092
|
+
inSection = false;
|
|
4093
|
+
continue;
|
|
4094
|
+
}
|
|
4095
|
+
if (/^-\s+\*\*/.test(line) || /^-\s+[^\s]/.test(line)) count++;
|
|
4096
|
+
}
|
|
4097
|
+
return count;
|
|
4098
|
+
}
|
|
4099
|
+
async function countCodeFiles(srcDir) {
|
|
4100
|
+
const exts = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".kt"]);
|
|
4101
|
+
let count = 0;
|
|
4102
|
+
async function walk(dir) {
|
|
4103
|
+
let entries;
|
|
4104
|
+
try {
|
|
4105
|
+
entries = await fs11.readdir(dir);
|
|
4106
|
+
} catch {
|
|
4107
|
+
return;
|
|
4108
|
+
}
|
|
4109
|
+
for (const name of entries) {
|
|
4110
|
+
if (name.startsWith(".") || name === "node_modules") continue;
|
|
4111
|
+
const full = path6.join(dir, name);
|
|
4112
|
+
const stat = await fs11.stat(full).catch(() => null);
|
|
4113
|
+
if (!stat) continue;
|
|
4114
|
+
if (stat.isDirectory()) {
|
|
4115
|
+
await walk(full);
|
|
4116
|
+
} else if (exts.has(path6.extname(name))) {
|
|
4117
|
+
count++;
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
await walk(srcDir);
|
|
4122
|
+
return count;
|
|
4123
|
+
}
|
|
4124
|
+
var EVAL_GATE_WINDOW_MS, STAGE_GATES;
|
|
4125
|
+
var init_gates = __esm({
|
|
4126
|
+
"src/core/gates.ts"() {
|
|
4127
|
+
"use strict";
|
|
4128
|
+
init_roboco();
|
|
4129
|
+
init_config();
|
|
4130
|
+
EVAL_GATE_WINDOW_MS = 60 * 60 * 1e3;
|
|
4131
|
+
STAGE_GATES = {
|
|
4132
|
+
environment: async () => makeResult("environment.entry", []),
|
|
4133
|
+
// 항상 통과
|
|
4134
|
+
design: designEntryGate,
|
|
4135
|
+
execution: executionEntryGate,
|
|
4136
|
+
validation: validationEntryGate,
|
|
4137
|
+
evaluation: evaluationEntryGate
|
|
4138
|
+
};
|
|
4139
|
+
}
|
|
4140
|
+
});
|
|
4141
|
+
|
|
3595
4142
|
// src/stages/environment/doctor.ts
|
|
3596
4143
|
var doctor_exports = {};
|
|
3597
4144
|
__export(doctor_exports, {
|
|
@@ -3599,7 +4146,7 @@ __export(doctor_exports, {
|
|
|
3599
4146
|
});
|
|
3600
4147
|
import { join as join6 } from "path";
|
|
3601
4148
|
import { homedir } from "os";
|
|
3602
|
-
import
|
|
4149
|
+
import fs12 from "fs-extra";
|
|
3603
4150
|
async function runDoctor() {
|
|
3604
4151
|
section("PAI Doctor \u2014 \uD658\uACBD \uC9C4\uB2E8");
|
|
3605
4152
|
const checks = [];
|
|
@@ -3619,7 +4166,7 @@ async function runDoctor() {
|
|
|
3619
4166
|
fix: claudeCheck.ok ? void 0 : "npm install -g @anthropic-ai/claude-code"
|
|
3620
4167
|
});
|
|
3621
4168
|
const globalConfigPath = join6(homedir(), ".pai", "config.json");
|
|
3622
|
-
const hasGlobalConfig = await
|
|
4169
|
+
const hasGlobalConfig = await fs12.pathExists(globalConfigPath);
|
|
3623
4170
|
checks.push({
|
|
3624
4171
|
label: "\uAE00\uB85C\uBC8C \uC124\uC815",
|
|
3625
4172
|
ok: true,
|
|
@@ -3637,6 +4184,25 @@ async function runDoctor() {
|
|
|
3637
4184
|
// optional이므로 항상 OK
|
|
3638
4185
|
detail: hasSdk ? "\uC124\uCE58\uB428 (AI \uAE30\uB2A5 \uD65C\uC131\uD654)" : "\uBBF8\uC124\uCE58 (\uC815\uC801 \uBD84\uC11D \uBAA8\uB4DC)"
|
|
3639
4186
|
});
|
|
4187
|
+
const { validateEdges: validateEdges2 } = await Promise.resolve().then(() => (init_gates(), gates_exports));
|
|
4188
|
+
const edgeViolations = await validateEdges2(process.cwd());
|
|
4189
|
+
if (edgeViolations.length > 0) {
|
|
4190
|
+
checks.push({
|
|
4191
|
+
label: "\uACC4\uC57D \uBB34\uACB0\uC131 (edges)",
|
|
4192
|
+
ok: false,
|
|
4193
|
+
detail: `${edgeViolations.length}\uAC74 \uC704\uBC18`,
|
|
4194
|
+
fix: edgeViolations.map((v) => v.message).join("; ")
|
|
4195
|
+
});
|
|
4196
|
+
} else {
|
|
4197
|
+
const { loadConfig: loadCfg } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4198
|
+
const cfg = await loadCfg(process.cwd());
|
|
4199
|
+
const edgeCount = cfg?.edges?.length ?? 0;
|
|
4200
|
+
checks.push({
|
|
4201
|
+
label: "\uACC4\uC57D \uBB34\uACB0\uC131 (edges)",
|
|
4202
|
+
ok: true,
|
|
4203
|
+
detail: edgeCount > 0 ? `${edgeCount}\uAC1C \uC5E3\uC9C0 \uBAA8\uB450 \uC720\uD6A8` : "\uC5E3\uC9C0 \uC5C6\uC74C (\uC815\uC0C1)"
|
|
4204
|
+
});
|
|
4205
|
+
}
|
|
3640
4206
|
console.log("");
|
|
3641
4207
|
let passed = 0;
|
|
3642
4208
|
for (const check of checks) {
|
|
@@ -3675,8 +4241,8 @@ var init_doctor = __esm({
|
|
|
3675
4241
|
});
|
|
3676
4242
|
|
|
3677
4243
|
// src/utils/github-fetch.ts
|
|
3678
|
-
import
|
|
3679
|
-
import
|
|
4244
|
+
import path7 from "path";
|
|
4245
|
+
import fs13 from "fs-extra";
|
|
3680
4246
|
async function httpGet(url, timeoutMs, accept) {
|
|
3681
4247
|
const controller = new AbortController();
|
|
3682
4248
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -3707,8 +4273,8 @@ async function downloadFile(downloadUrl, destPath, timeoutMs) {
|
|
|
3707
4273
|
throw new Error(`Download failed ${res.status} ${res.statusText} (${downloadUrl})`);
|
|
3708
4274
|
}
|
|
3709
4275
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
3710
|
-
await
|
|
3711
|
-
await
|
|
4276
|
+
await fs13.ensureDir(path7.dirname(destPath));
|
|
4277
|
+
await fs13.writeFile(destPath, buf);
|
|
3712
4278
|
}
|
|
3713
4279
|
async function fetchGithubDir(opts) {
|
|
3714
4280
|
const {
|
|
@@ -3732,10 +4298,10 @@ async function fetchGithubDir(opts) {
|
|
|
3732
4298
|
for (const entry of entries) {
|
|
3733
4299
|
const relName = entry.name;
|
|
3734
4300
|
if (entry.type === "dir") {
|
|
3735
|
-
await walk(`${currentSrc}/${relName}`,
|
|
4301
|
+
await walk(`${currentSrc}/${relName}`, path7.join(currentDest, relName));
|
|
3736
4302
|
} else if (entry.type === "file") {
|
|
3737
|
-
const destFile =
|
|
3738
|
-
if (!overwrite && await
|
|
4303
|
+
const destFile = path7.join(currentDest, relName);
|
|
4304
|
+
if (!overwrite && await fs13.pathExists(destFile)) {
|
|
3739
4305
|
result.skipped.push(destFile);
|
|
3740
4306
|
continue;
|
|
3741
4307
|
}
|
|
@@ -3767,8 +4333,8 @@ var fetch_cmd_exports = {};
|
|
|
3767
4333
|
__export(fetch_cmd_exports, {
|
|
3768
4334
|
fetchCommand: () => fetchCommand
|
|
3769
4335
|
});
|
|
3770
|
-
import
|
|
3771
|
-
import
|
|
4336
|
+
import path8 from "path";
|
|
4337
|
+
import fs14 from "fs-extra";
|
|
3772
4338
|
import chalk4 from "chalk";
|
|
3773
4339
|
async function fetchCommand(cwd, recipeKey, options) {
|
|
3774
4340
|
if (options.list) {
|
|
@@ -3806,7 +4372,7 @@ async function fetchCommand(cwd, recipeKey, options) {
|
|
|
3806
4372
|
const installed = [];
|
|
3807
4373
|
for (const key of keys) {
|
|
3808
4374
|
const recipe = RECIPES[key];
|
|
3809
|
-
const targetDir =
|
|
4375
|
+
const targetDir = path8.join(cwd, recipe.target);
|
|
3810
4376
|
console.log("");
|
|
3811
4377
|
console.log(` ${colors.accent(key)} \u2014 ${recipe.label}`);
|
|
3812
4378
|
const result = await withSpinner(`\uB2E4\uC6B4\uB85C\uB4DC \uC911...`, async () => {
|
|
@@ -3828,7 +4394,7 @@ async function fetchCommand(cwd, recipeKey, options) {
|
|
|
3828
4394
|
if (result.written.length > 0) {
|
|
3829
4395
|
success(`${result.written.length}\uAC1C \uD30C\uC77C \uC800\uC7A5`);
|
|
3830
4396
|
for (const f of result.written) {
|
|
3831
|
-
console.log(chalk4.gray(` ${
|
|
4397
|
+
console.log(chalk4.gray(` ${path8.relative(cwd, f)}`));
|
|
3832
4398
|
}
|
|
3833
4399
|
}
|
|
3834
4400
|
if (result.skipped.length > 0) {
|
|
@@ -3864,10 +4430,10 @@ async function fetchCommand(cwd, recipeKey, options) {
|
|
|
3864
4430
|
}
|
|
3865
4431
|
async function appendEnvKeys(cwd, recipe) {
|
|
3866
4432
|
if (recipe.envKeys.length === 0) return;
|
|
3867
|
-
const envPath =
|
|
4433
|
+
const envPath = path8.join(cwd, ".env.local");
|
|
3868
4434
|
let content = "";
|
|
3869
|
-
if (await
|
|
3870
|
-
content = await
|
|
4435
|
+
if (await fs14.pathExists(envPath)) {
|
|
4436
|
+
content = await fs14.readFile(envPath, "utf8");
|
|
3871
4437
|
}
|
|
3872
4438
|
const missingKeys = recipe.envKeys.filter((ek) => !content.includes(`${ek.key}=`));
|
|
3873
4439
|
if (missingKeys.length === 0) return;
|
|
@@ -3880,13 +4446,13 @@ async function appendEnvKeys(cwd, recipe) {
|
|
|
3880
4446
|
if (ek.hint) lines.push(`# ${ek.hint}`);
|
|
3881
4447
|
lines.push(`${ek.key}=${ek.default ?? ""}`);
|
|
3882
4448
|
}
|
|
3883
|
-
await
|
|
3884
|
-
await
|
|
4449
|
+
await fs14.ensureFile(envPath);
|
|
4450
|
+
await fs14.appendFile(envPath, lines.join("\n") + "\n");
|
|
3885
4451
|
}
|
|
3886
4452
|
async function upsertRecipesSkill(cwd, installedKeys) {
|
|
3887
|
-
const skillDir =
|
|
3888
|
-
await
|
|
3889
|
-
const skillPath =
|
|
4453
|
+
const skillDir = path8.join(cwd, ".claude", "skills", "recipes");
|
|
4454
|
+
await fs14.ensureDir(skillDir);
|
|
4455
|
+
const skillPath = path8.join(skillDir, "SKILL.md");
|
|
3890
4456
|
const recipes = installedKeys.map((k) => RECIPES[k]);
|
|
3891
4457
|
const triggers = recipes.map((r) => r.skillDescription ?? `${r.label} \uAD00\uB828 \uAE30\uB2A5 \uAD6C\uD604 \uC2DC ${r.target}/ \uCC38\uC870`).join("\n- ");
|
|
3892
4458
|
const body = [
|
|
@@ -3925,10 +4491,10 @@ async function upsertRecipesSkill(cwd, installedKeys) {
|
|
|
3925
4491
|
"5. \uAC00\uC774\uB4DC \uC21C\uC11C\uC5D0 \uB530\uB77C \uAD6C\uD604 \u2014 \uB808\uC2DC\uD53C \uADDC\uCE59\uC744 \uC808\uB300 \uC6B0\uD68C\uD558\uC9C0 \uB9D0 \uAC83",
|
|
3926
4492
|
""
|
|
3927
4493
|
].join("\n");
|
|
3928
|
-
await
|
|
4494
|
+
await fs14.writeFile(skillPath, body);
|
|
3929
4495
|
}
|
|
3930
4496
|
async function upsertClaudeMdBlock(cwd, installedKeys) {
|
|
3931
|
-
const claudeMdPath =
|
|
4497
|
+
const claudeMdPath = path8.join(cwd, "CLAUDE.md");
|
|
3932
4498
|
const BLOCK_START = "<!-- pai:recipes:start -->";
|
|
3933
4499
|
const BLOCK_END = "<!-- pai:recipes:end -->";
|
|
3934
4500
|
const lines = [];
|
|
@@ -3948,8 +4514,8 @@ async function upsertClaudeMdBlock(cwd, installedKeys) {
|
|
|
3948
4514
|
lines.push(BLOCK_END);
|
|
3949
4515
|
const block = lines.join("\n");
|
|
3950
4516
|
let content = "";
|
|
3951
|
-
if (await
|
|
3952
|
-
content = await
|
|
4517
|
+
if (await fs14.pathExists(claudeMdPath)) {
|
|
4518
|
+
content = await fs14.readFile(claudeMdPath, "utf8");
|
|
3953
4519
|
}
|
|
3954
4520
|
const startIdx = content.indexOf(BLOCK_START);
|
|
3955
4521
|
const endIdx = content.indexOf(BLOCK_END);
|
|
@@ -3962,8 +4528,8 @@ async function upsertClaudeMdBlock(cwd, installedKeys) {
|
|
|
3962
4528
|
if (content.length > 0) content += "\n";
|
|
3963
4529
|
content += block + "\n";
|
|
3964
4530
|
}
|
|
3965
|
-
await
|
|
3966
|
-
await
|
|
4531
|
+
await fs14.ensureFile(claudeMdPath);
|
|
4532
|
+
await fs14.writeFile(claudeMdPath, content);
|
|
3967
4533
|
}
|
|
3968
4534
|
var init_fetch_cmd = __esm({
|
|
3969
4535
|
"src/cli/commands/fetch.cmd.ts"() {
|
|
@@ -4197,7 +4763,7 @@ __export(analyzer_exports2, {
|
|
|
4197
4763
|
analyzeRepository: () => analyzeRepository
|
|
4198
4764
|
});
|
|
4199
4765
|
import { join as join7 } from "path";
|
|
4200
|
-
import
|
|
4766
|
+
import fs15 from "fs-extra";
|
|
4201
4767
|
async function analyzeRepository(repoPath) {
|
|
4202
4768
|
try {
|
|
4203
4769
|
return await aiAnalysis(repoPath);
|
|
@@ -4258,14 +4824,14 @@ async function checkTestCoverage(repoPath) {
|
|
|
4258
4824
|
".nycrc"
|
|
4259
4825
|
];
|
|
4260
4826
|
for (const f of testConfigs) {
|
|
4261
|
-
const found = await
|
|
4827
|
+
const found = await fs15.pathExists(join7(repoPath, f));
|
|
4262
4828
|
findings.push({ item: f, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4263
4829
|
if (found) score += 20;
|
|
4264
4830
|
}
|
|
4265
4831
|
const testDirs = ["tests", "test", "__tests__", "spec"];
|
|
4266
4832
|
let hasTestDir = false;
|
|
4267
4833
|
for (const d of testDirs) {
|
|
4268
|
-
if (await
|
|
4834
|
+
if (await fs15.pathExists(join7(repoPath, d))) {
|
|
4269
4835
|
findings.push({ item: d, found: true, details: "\uD14C\uC2A4\uD2B8 \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC" });
|
|
4270
4836
|
hasTestDir = true;
|
|
4271
4837
|
score += 30;
|
|
@@ -4288,8 +4854,8 @@ async function checkCiCd(repoPath) {
|
|
|
4288
4854
|
{ path: "Jenkinsfile", label: "Jenkins" },
|
|
4289
4855
|
{ path: ".circleci", label: "CircleCI" }
|
|
4290
4856
|
];
|
|
4291
|
-
for (const { path:
|
|
4292
|
-
const found = await
|
|
4857
|
+
for (const { path: path10, label } of ciConfigs) {
|
|
4858
|
+
const found = await fs15.pathExists(join7(repoPath, path10));
|
|
4293
4859
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4294
4860
|
if (found) score += 40;
|
|
4295
4861
|
}
|
|
@@ -4307,8 +4873,8 @@ async function checkHooks(repoPath) {
|
|
|
4307
4873
|
{ path: "commitlint.config.js", label: "commitlint" },
|
|
4308
4874
|
{ path: ".claude/settings.json", label: "Claude Code settings" }
|
|
4309
4875
|
];
|
|
4310
|
-
for (const { path:
|
|
4311
|
-
const found = await
|
|
4876
|
+
for (const { path: path10, label } of hookConfigs) {
|
|
4877
|
+
const found = await fs15.pathExists(join7(repoPath, path10));
|
|
4312
4878
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4313
4879
|
if (found) score += 20;
|
|
4314
4880
|
}
|
|
@@ -4325,8 +4891,8 @@ async function checkRepoStructure(repoPath) {
|
|
|
4325
4891
|
{ path: ".env.example", label: "\uD658\uACBD\uBCC0\uC218 \uC608\uC2DC" },
|
|
4326
4892
|
{ path: ".gitignore", label: ".gitignore" }
|
|
4327
4893
|
];
|
|
4328
|
-
for (const { path:
|
|
4329
|
-
const found = await
|
|
4894
|
+
for (const { path: path10, label } of structureChecks) {
|
|
4895
|
+
const found = await fs15.pathExists(join7(repoPath, path10));
|
|
4330
4896
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4331
4897
|
if (found) score += 25;
|
|
4332
4898
|
}
|
|
@@ -4342,8 +4908,8 @@ async function checkDocumentation(repoPath) {
|
|
|
4342
4908
|
{ path: "docs", label: "docs/ \uB514\uB809\uD1A0\uB9AC", points: 25 },
|
|
4343
4909
|
{ path: "docs/openspec.md", label: "OpenSpec PRD", points: 25 }
|
|
4344
4910
|
];
|
|
4345
|
-
for (const { path:
|
|
4346
|
-
const found = await
|
|
4911
|
+
for (const { path: path10, label, points } of docChecks) {
|
|
4912
|
+
const found = await fs15.pathExists(join7(repoPath, path10));
|
|
4347
4913
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4348
4914
|
if (found) score += points;
|
|
4349
4915
|
}
|
|
@@ -4361,8 +4927,8 @@ async function checkHarnessEngineering(repoPath) {
|
|
|
4361
4927
|
{ path: ".claude/commands", label: ".claude/commands/", points: 10 },
|
|
4362
4928
|
{ path: ".pai/config.json", label: "PAI config", points: 10 }
|
|
4363
4929
|
];
|
|
4364
|
-
for (const { path:
|
|
4365
|
-
const found = await
|
|
4930
|
+
for (const { path: path10, label, points } of harnessChecks) {
|
|
4931
|
+
const found = await fs15.pathExists(join7(repoPath, path10));
|
|
4366
4932
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4367
4933
|
if (found) score += points;
|
|
4368
4934
|
}
|
|
@@ -4587,13 +5153,13 @@ function buildMarkdownReport(result) {
|
|
|
4587
5153
|
return lines.join("\n") + "\n";
|
|
4588
5154
|
}
|
|
4589
5155
|
function buildDetailedReport(result, projectName) {
|
|
4590
|
-
const
|
|
5156
|
+
const now2 = (/* @__PURE__ */ new Date()).toLocaleString("ko-KR");
|
|
4591
5157
|
const mustCats = result.categories.filter((c2) => c2.tier === "must");
|
|
4592
5158
|
const niceCats = result.categories.filter((c2) => c2.tier === "nice");
|
|
4593
5159
|
const lines = [
|
|
4594
5160
|
`# ${projectName} \uBC14\uC774\uBE0C \uCF54\uB529 \uC900\uBE44\uB3C4 \uBD84\uC11D \uB9AC\uD3EC\uD2B8`,
|
|
4595
5161
|
"",
|
|
4596
|
-
`> \uC2A4\uCE94 \uC77C\uC2DC: ${
|
|
5162
|
+
`> \uC2A4\uCE94 \uC77C\uC2DC: ${now2}`,
|
|
4597
5163
|
`> \uBD84\uC11D \uB300\uC0C1: ${projectName}`,
|
|
4598
5164
|
"",
|
|
4599
5165
|
"---",
|
|
@@ -4747,54 +5313,54 @@ __export(shell_cd_exports, {
|
|
|
4747
5313
|
});
|
|
4748
5314
|
import { join as join8 } from "path";
|
|
4749
5315
|
import { homedir as homedir2 } from "os";
|
|
4750
|
-
import
|
|
5316
|
+
import fs16 from "fs-extra";
|
|
4751
5317
|
async function requestCdAfter(targetDir) {
|
|
4752
|
-
await
|
|
4753
|
-
await
|
|
5318
|
+
await fs16.ensureDir(PAI_DIR);
|
|
5319
|
+
await fs16.writeFile(CD_FILE, targetDir);
|
|
4754
5320
|
}
|
|
4755
5321
|
async function installShellHelper() {
|
|
4756
|
-
await
|
|
5322
|
+
await fs16.ensureDir(PAI_DIR);
|
|
4757
5323
|
if (isWindows) {
|
|
4758
5324
|
return installPowerShellHelper();
|
|
4759
5325
|
}
|
|
4760
5326
|
return installBashHelper();
|
|
4761
5327
|
}
|
|
4762
5328
|
async function installBashHelper() {
|
|
4763
|
-
await
|
|
5329
|
+
await fs16.writeFile(HELPER_FILE_SH, BASH_HELPER);
|
|
4764
5330
|
const rcFile = getShellRcPath();
|
|
4765
5331
|
const sourceLine = 'source "$HOME/.pai/shell-helper.sh"';
|
|
4766
|
-
if (await
|
|
4767
|
-
const content = await
|
|
5332
|
+
if (await fs16.pathExists(rcFile)) {
|
|
5333
|
+
const content = await fs16.readFile(rcFile, "utf8");
|
|
4768
5334
|
if (content.includes("shell-helper.sh")) {
|
|
4769
5335
|
return true;
|
|
4770
5336
|
}
|
|
4771
|
-
await
|
|
5337
|
+
await fs16.appendFile(rcFile, `
|
|
4772
5338
|
# PAI \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9
|
|
4773
5339
|
${sourceLine}
|
|
4774
5340
|
`);
|
|
4775
5341
|
return false;
|
|
4776
5342
|
}
|
|
4777
|
-
await
|
|
5343
|
+
await fs16.writeFile(rcFile, `${sourceLine}
|
|
4778
5344
|
`);
|
|
4779
5345
|
return false;
|
|
4780
5346
|
}
|
|
4781
5347
|
async function installPowerShellHelper() {
|
|
4782
|
-
await
|
|
5348
|
+
await fs16.writeFile(HELPER_FILE_PS1, POWERSHELL_HELPER);
|
|
4783
5349
|
const rcFile = getShellRcPath();
|
|
4784
5350
|
const sourceLine = '. "$env:USERPROFILE\\.pai\\shell-helper.ps1"';
|
|
4785
|
-
await
|
|
4786
|
-
if (await
|
|
4787
|
-
const content = await
|
|
5351
|
+
await fs16.ensureDir(join8(rcFile, ".."));
|
|
5352
|
+
if (await fs16.pathExists(rcFile)) {
|
|
5353
|
+
const content = await fs16.readFile(rcFile, "utf8");
|
|
4788
5354
|
if (content.includes("shell-helper.ps1")) {
|
|
4789
5355
|
return true;
|
|
4790
5356
|
}
|
|
4791
|
-
await
|
|
5357
|
+
await fs16.appendFile(rcFile, `
|
|
4792
5358
|
# PAI \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9
|
|
4793
5359
|
${sourceLine}
|
|
4794
5360
|
`);
|
|
4795
5361
|
return false;
|
|
4796
5362
|
}
|
|
4797
|
-
await
|
|
5363
|
+
await fs16.writeFile(rcFile, `${sourceLine}
|
|
4798
5364
|
`);
|
|
4799
5365
|
return false;
|
|
4800
5366
|
}
|
|
@@ -4856,11 +5422,11 @@ __export(claude_settings_exports, {
|
|
|
4856
5422
|
isAlreadyEnabled: () => isAlreadyEnabled,
|
|
4857
5423
|
mergeOmcIntoSettings: () => mergeOmcIntoSettings
|
|
4858
5424
|
});
|
|
4859
|
-
import
|
|
4860
|
-
import
|
|
4861
|
-
import
|
|
4862
|
-
function getClaudeSettingsPath(homeDir =
|
|
4863
|
-
return
|
|
5425
|
+
import os3 from "os";
|
|
5426
|
+
import path9 from "path";
|
|
5427
|
+
import fs17 from "fs-extra";
|
|
5428
|
+
function getClaudeSettingsPath(homeDir = os3.homedir()) {
|
|
5429
|
+
return path9.join(homeDir, ".claude", "settings.json");
|
|
4864
5430
|
}
|
|
4865
5431
|
function parseJsonWithBom(raw) {
|
|
4866
5432
|
const stripped = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
@@ -4875,13 +5441,13 @@ async function enableOmcPlugin(options = {}) {
|
|
|
4875
5441
|
const pluginId = options.pluginId ?? DEFAULT_PLUGIN_ID;
|
|
4876
5442
|
const wantBackup = options.backup ?? true;
|
|
4877
5443
|
const settingsPath = getClaudeSettingsPath();
|
|
4878
|
-
await
|
|
4879
|
-
if (!await
|
|
5444
|
+
await fs17.ensureDir(path9.dirname(settingsPath));
|
|
5445
|
+
if (!await fs17.pathExists(settingsPath)) {
|
|
4880
5446
|
const skeleton = buildSkeleton(marketplaceId, marketplaceUrl, pluginId);
|
|
4881
|
-
await
|
|
5447
|
+
await fs17.writeFile(settingsPath, JSON.stringify(skeleton, null, 2) + "\n", "utf8");
|
|
4882
5448
|
return { action: "created", settingsPath };
|
|
4883
5449
|
}
|
|
4884
|
-
const raw = await
|
|
5450
|
+
const raw = await fs17.readFile(settingsPath, "utf8");
|
|
4885
5451
|
let parsed;
|
|
4886
5452
|
try {
|
|
4887
5453
|
const value = parseJsonWithBom(raw);
|
|
@@ -4891,7 +5457,7 @@ async function enableOmcPlugin(options = {}) {
|
|
|
4891
5457
|
parsed = value;
|
|
4892
5458
|
} catch (err) {
|
|
4893
5459
|
const backupPath2 = `${settingsPath}.backup-${timestampSuffix()}`;
|
|
4894
|
-
await
|
|
5460
|
+
await fs17.copy(settingsPath, backupPath2);
|
|
4895
5461
|
throw new ClaudeSettingsError(
|
|
4896
5462
|
`settings.json \uD30C\uC2F1 \uC2E4\uD328: ${err.message}. \uBC31\uC5C5: ${backupPath2}`,
|
|
4897
5463
|
backupPath2
|
|
@@ -4903,10 +5469,10 @@ async function enableOmcPlugin(options = {}) {
|
|
|
4903
5469
|
let backupPath;
|
|
4904
5470
|
if (wantBackup) {
|
|
4905
5471
|
backupPath = `${settingsPath}.backup-${timestampSuffix()}`;
|
|
4906
|
-
await
|
|
5472
|
+
await fs17.copy(settingsPath, backupPath);
|
|
4907
5473
|
}
|
|
4908
5474
|
const merged = mergeOmcIntoSettings(parsed, marketplaceId, marketplaceUrl, pluginId);
|
|
4909
|
-
await
|
|
5475
|
+
await fs17.writeFile(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
4910
5476
|
return { action: "added", settingsPath, backupPath };
|
|
4911
5477
|
}
|
|
4912
5478
|
function buildSkeleton(marketplaceId, marketplaceUrl, pluginId) {
|
|
@@ -5005,13 +5571,13 @@ function getCachedResult(repoPath) {
|
|
|
5005
5571
|
function setCachedResult(repoPath, llmOutput) {
|
|
5006
5572
|
const store = loadCache(repoPath);
|
|
5007
5573
|
const repoHash = computeRepoHash(repoPath);
|
|
5008
|
-
const
|
|
5574
|
+
const now2 = Date.now();
|
|
5009
5575
|
for (const [key, entry] of Object.entries(store.entries)) {
|
|
5010
|
-
if (
|
|
5576
|
+
if (now2 - entry.timestamp > CACHE_TTL_MS) {
|
|
5011
5577
|
delete store.entries[key];
|
|
5012
5578
|
}
|
|
5013
5579
|
}
|
|
5014
|
-
store.entries[repoHash] = { repoPath, repoHash, timestamp:
|
|
5580
|
+
store.entries[repoHash] = { repoPath, repoHash, timestamp: now2, llmOutput };
|
|
5015
5581
|
saveCache(repoPath, store);
|
|
5016
5582
|
}
|
|
5017
5583
|
var CACHE_DIR, CACHE_FILE, CACHE_TTL_MS, FILES_TO_HASH;
|
|
@@ -5042,59 +5608,270 @@ var init_cache = __esm({
|
|
|
5042
5608
|
}
|
|
5043
5609
|
});
|
|
5044
5610
|
|
|
5045
|
-
// src/
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
if (useCache) {
|
|
5060
|
-
setCachedResult(cwd, llmOutput);
|
|
5611
|
+
// src/stages/validation/runner.ts
|
|
5612
|
+
import { join as join10 } from "path";
|
|
5613
|
+
import fs18 from "fs-extra";
|
|
5614
|
+
async function runTests(cwd) {
|
|
5615
|
+
const start = Date.now();
|
|
5616
|
+
const gstackPath = join10(cwd, ".pai", "gstack.json");
|
|
5617
|
+
let runner = "npm test";
|
|
5618
|
+
if (await fs18.pathExists(gstackPath)) {
|
|
5619
|
+
try {
|
|
5620
|
+
const config = await fs18.readJson(gstackPath);
|
|
5621
|
+
if (config.testRunner === "vitest") runner = "npx vitest run";
|
|
5622
|
+
else if (config.testRunner === "jest") runner = "npx jest";
|
|
5623
|
+
else if (config.testRunner === "mocha") runner = "npx mocha";
|
|
5624
|
+
} catch {
|
|
5061
5625
|
}
|
|
5062
5626
|
}
|
|
5063
|
-
const
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
|
|
5078
|
-
console.log("");
|
|
5079
|
-
console.log(detailedReport);
|
|
5080
|
-
if (options.output) {
|
|
5081
|
-
await fs16.writeFile(options.output, detailedReport, "utf8");
|
|
5082
|
-
success(`\uCD94\uAC00 \uC800\uC7A5: ${options.output}`);
|
|
5627
|
+
const pkgPath = join10(cwd, "package.json");
|
|
5628
|
+
if (await fs18.pathExists(pkgPath)) {
|
|
5629
|
+
try {
|
|
5630
|
+
const pkg5 = await fs18.readJson(pkgPath);
|
|
5631
|
+
if (!pkg5.scripts?.test || pkg5.scripts.test.includes("no test specified")) {
|
|
5632
|
+
return {
|
|
5633
|
+
runner,
|
|
5634
|
+
passed: false,
|
|
5635
|
+
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.",
|
|
5636
|
+
duration: Date.now() - start
|
|
5637
|
+
};
|
|
5638
|
+
}
|
|
5639
|
+
} catch {
|
|
5640
|
+
}
|
|
5083
5641
|
}
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5642
|
+
try {
|
|
5643
|
+
const { execa } = await import("execa");
|
|
5644
|
+
const { stdout, stderr } = await execa("npm", ["test"], {
|
|
5645
|
+
cwd,
|
|
5646
|
+
timeout: 12e4,
|
|
5647
|
+
env: { ...process.env, CI: "true" }
|
|
5648
|
+
});
|
|
5649
|
+
return {
|
|
5650
|
+
runner,
|
|
5651
|
+
passed: true,
|
|
5652
|
+
output: stdout || stderr,
|
|
5653
|
+
duration: Date.now() - start
|
|
5654
|
+
};
|
|
5655
|
+
} catch (err) {
|
|
5656
|
+
const output = err instanceof Error ? err.message : String(err);
|
|
5657
|
+
return {
|
|
5658
|
+
runner,
|
|
5659
|
+
passed: false,
|
|
5660
|
+
output,
|
|
5661
|
+
duration: Date.now() - start
|
|
5662
|
+
};
|
|
5087
5663
|
}
|
|
5088
5664
|
}
|
|
5089
|
-
var
|
|
5090
|
-
"src/
|
|
5665
|
+
var init_runner = __esm({
|
|
5666
|
+
"src/stages/validation/runner.ts"() {
|
|
5091
5667
|
"use strict";
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5668
|
+
}
|
|
5669
|
+
});
|
|
5670
|
+
|
|
5671
|
+
// src/stages/validation/harness.ts
|
|
5672
|
+
import { join as join11 } from "path";
|
|
5673
|
+
import fs19 from "fs-extra";
|
|
5674
|
+
async function runHarnessCheck(cwd) {
|
|
5675
|
+
const harnessPath = join11(cwd, ".pai", "harness.json");
|
|
5676
|
+
if (!await fs19.pathExists(harnessPath)) {
|
|
5677
|
+
return { enabled: false, specFile: null, rules: [], checks: [] };
|
|
5678
|
+
}
|
|
5679
|
+
let config;
|
|
5680
|
+
try {
|
|
5681
|
+
config = await fs19.readJson(harnessPath);
|
|
5682
|
+
} catch {
|
|
5683
|
+
return { enabled: false, specFile: null, rules: [], checks: [] };
|
|
5684
|
+
}
|
|
5685
|
+
const specFile = config.specFile ?? "docs/openspec.md";
|
|
5686
|
+
const rules = config.rules ?? [];
|
|
5687
|
+
const checks = [];
|
|
5688
|
+
if (rules.includes("spec-implementation-match")) {
|
|
5689
|
+
const specExists = await fs19.pathExists(join11(cwd, specFile));
|
|
5690
|
+
const srcExists = await fs19.pathExists(join11(cwd, "src"));
|
|
5691
|
+
checks.push({
|
|
5692
|
+
rule: "spec-implementation-match",
|
|
5693
|
+
passed: specExists && srcExists,
|
|
5694
|
+
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()
|
|
5695
|
+
});
|
|
5696
|
+
}
|
|
5697
|
+
if (rules.includes("api-contract-test")) {
|
|
5698
|
+
const testDir = await fs19.pathExists(join11(cwd, "tests"));
|
|
5699
|
+
const testDir2 = await fs19.pathExists(join11(cwd, "test"));
|
|
5700
|
+
checks.push({
|
|
5701
|
+
rule: "api-contract-test",
|
|
5702
|
+
passed: testDir || testDir2,
|
|
5703
|
+
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"
|
|
5704
|
+
});
|
|
5705
|
+
}
|
|
5706
|
+
return { enabled: true, specFile, rules, checks };
|
|
5707
|
+
}
|
|
5708
|
+
var init_harness = __esm({
|
|
5709
|
+
"src/stages/validation/harness.ts"() {
|
|
5710
|
+
"use strict";
|
|
5711
|
+
}
|
|
5712
|
+
});
|
|
5713
|
+
|
|
5714
|
+
// src/cli/commands/validate.cmd.ts
|
|
5715
|
+
var validate_cmd_exports = {};
|
|
5716
|
+
__export(validate_cmd_exports, {
|
|
5717
|
+
handleRobocoCliError: () => handleRobocoError,
|
|
5718
|
+
validateCommand: () => validateCommand
|
|
5719
|
+
});
|
|
5720
|
+
async function validateCommand(cwd, options = {}) {
|
|
5721
|
+
try {
|
|
5722
|
+
await withRoboco(
|
|
5723
|
+
cwd,
|
|
5724
|
+
"validation",
|
|
5725
|
+
"pai test",
|
|
5726
|
+
{ skipGates: options.skipGates, force: options.force, gate: STAGE_GATES.validation },
|
|
5727
|
+
async () => {
|
|
5728
|
+
section("\uD14C\uC2A4\uD2B8 \uC2E4\uD589");
|
|
5729
|
+
const testResult = await runTests(cwd);
|
|
5730
|
+
if (testResult.passed) {
|
|
5731
|
+
success(`\uD14C\uC2A4\uD2B8 \uD1B5\uACFC (${testResult.runner}, ${testResult.duration}ms)`);
|
|
5732
|
+
} else {
|
|
5733
|
+
error("\uD14C\uC2A4\uD2B8 \uC2E4\uD328");
|
|
5734
|
+
info(testResult.output.slice(0, 300));
|
|
5735
|
+
}
|
|
5736
|
+
section("\uD558\uB124\uC2A4 \uAC80\uC99D");
|
|
5737
|
+
const harness = await runHarnessCheck(cwd);
|
|
5738
|
+
if (!harness.enabled) {
|
|
5739
|
+
info("Harness \uC124\uC815 \uC5C6\uC74C \u2014 \uAC74\uB108\uB700");
|
|
5740
|
+
info("\uC124\uC815 \uCD94\uAC00: `pai add` \uC5D0\uC11C Harness Engineering \uC120\uD0DD");
|
|
5741
|
+
} else {
|
|
5742
|
+
for (const check of harness.checks) {
|
|
5743
|
+
if (check.passed) {
|
|
5744
|
+
success(`${check.rule}: ${check.detail}`);
|
|
5745
|
+
} else {
|
|
5746
|
+
warn(`${check.rule}: ${check.detail}`);
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
}
|
|
5750
|
+
const allPassed = testResult.passed && harness.checks.every((c2) => c2.passed);
|
|
5751
|
+
console.log("");
|
|
5752
|
+
if (allPassed) {
|
|
5753
|
+
success("\uAC80\uC99D \uD1B5\uACFC!");
|
|
5754
|
+
} else if (!testResult.passed) {
|
|
5755
|
+
error("\uAC80\uC99D \uC2E4\uD328 \u2014 \uD14C\uC2A4\uD2B8\uB97C \uC218\uC815\uD558\uC138\uC694.");
|
|
5756
|
+
throw new Error("tests-failed");
|
|
5757
|
+
} else {
|
|
5758
|
+
warn("\uBD80\uBD84 \uD1B5\uACFC \u2014 \uD558\uB124\uC2A4 \uAC80\uC99D \uD56D\uBAA9\uC744 \uD655\uC778\uD558\uC138\uC694.");
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
);
|
|
5762
|
+
await syncHandoff(cwd).catch(() => {
|
|
5763
|
+
});
|
|
5764
|
+
} catch (err) {
|
|
5765
|
+
handleRobocoError(err);
|
|
5766
|
+
}
|
|
5767
|
+
}
|
|
5768
|
+
function handleRobocoError(err) {
|
|
5769
|
+
if (err instanceof RobocoLockError) {
|
|
5770
|
+
error(err.message);
|
|
5771
|
+
process.exitCode = 1;
|
|
5772
|
+
return;
|
|
5773
|
+
}
|
|
5774
|
+
if (err instanceof GateFailedError) {
|
|
5775
|
+
error(`\uB2E8\uACC4 \uC9C4\uC785 \uC2E4\uD328 (${err.result.name})`);
|
|
5776
|
+
for (const v of err.result.violations) {
|
|
5777
|
+
warn(` \xB7 ${v.message}${v.location ? ` [${v.location}]` : ""}`);
|
|
5778
|
+
}
|
|
5779
|
+
hint("\uC6B0\uD68C \uC635\uC158: --skip-gates (\uACBD\uACE0\uB9CC) / --force (\uAC8C\uC774\uD2B8 \uBB34\uC2DC)");
|
|
5780
|
+
process.exitCode = 1;
|
|
5781
|
+
return;
|
|
5782
|
+
}
|
|
5783
|
+
if (err instanceof Error) {
|
|
5784
|
+
if (err.message === "tests-failed") {
|
|
5785
|
+
process.exitCode = 1;
|
|
5786
|
+
return;
|
|
5787
|
+
}
|
|
5788
|
+
error(err.message);
|
|
5789
|
+
process.exitCode = 1;
|
|
5790
|
+
return;
|
|
5791
|
+
}
|
|
5792
|
+
throw err;
|
|
5793
|
+
}
|
|
5794
|
+
var init_validate_cmd = __esm({
|
|
5795
|
+
"src/cli/commands/validate.cmd.ts"() {
|
|
5796
|
+
"use strict";
|
|
5797
|
+
init_ui();
|
|
5798
|
+
init_runner();
|
|
5799
|
+
init_harness();
|
|
5800
|
+
init_roboco();
|
|
5801
|
+
init_gates();
|
|
5802
|
+
}
|
|
5803
|
+
});
|
|
5804
|
+
|
|
5805
|
+
// src/cli/commands/evaluate.cmd.ts
|
|
5806
|
+
var evaluate_cmd_exports = {};
|
|
5807
|
+
__export(evaluate_cmd_exports, {
|
|
5808
|
+
evaluateCommand: () => evaluateCommand
|
|
5809
|
+
});
|
|
5810
|
+
import { join as join12, basename } from "path";
|
|
5811
|
+
import fs20 from "fs-extra";
|
|
5812
|
+
async function evaluateCommand(cwd, options) {
|
|
5813
|
+
const useCache = options.cache !== false;
|
|
5814
|
+
try {
|
|
5815
|
+
await withRoboco(
|
|
5816
|
+
cwd,
|
|
5817
|
+
"evaluation",
|
|
5818
|
+
"pai grade",
|
|
5819
|
+
{ skipGates: options.skipGates, force: options.force, gate: STAGE_GATES.evaluation },
|
|
5820
|
+
async () => {
|
|
5821
|
+
let llmOutput = useCache ? getCachedResult(cwd) : null;
|
|
5822
|
+
if (llmOutput) {
|
|
5823
|
+
info("\uCE90\uC2DC\uB41C \uACB0\uACFC \uC0AC\uC6A9 (24\uC2DC\uAC04 \uC774\uB0B4)");
|
|
5824
|
+
} else {
|
|
5825
|
+
llmOutput = await analyzeRepository(cwd);
|
|
5826
|
+
if (useCache) setCachedResult(cwd, llmOutput);
|
|
5827
|
+
}
|
|
5828
|
+
const result = computeResult(llmOutput);
|
|
5829
|
+
printReport(result);
|
|
5830
|
+
if (options.verbose) printVerboseFindings(result);
|
|
5831
|
+
const config = await loadConfig(cwd);
|
|
5832
|
+
const projectName = config?.projectName ?? basename(cwd);
|
|
5833
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5834
|
+
const reportDir = join12(cwd, "docs", "p-reports");
|
|
5835
|
+
const reportPath = join12(reportDir, `${today}.md`);
|
|
5836
|
+
await fs20.ensureDir(reportDir);
|
|
5837
|
+
const detailedReport = buildDetailedReport(result, projectName);
|
|
5838
|
+
await fs20.writeFile(reportPath, detailedReport, "utf8");
|
|
5839
|
+
console.log("");
|
|
5840
|
+
success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
|
|
5841
|
+
console.log("");
|
|
5842
|
+
console.log(detailedReport);
|
|
5843
|
+
if (options.output) {
|
|
5844
|
+
await fs20.writeFile(options.output, detailedReport, "utf8");
|
|
5845
|
+
success(`\uCD94\uAC00 \uC800\uC7A5: ${options.output}`);
|
|
5846
|
+
}
|
|
5847
|
+
if (options.failUnder && result.totalScore < options.failUnder) {
|
|
5848
|
+
error(`\uC810\uC218 ${result.totalScore}\uC774 \uCD5C\uC18C \uAE30\uC900 ${options.failUnder}\uC5D0 \uBBF8\uB2EC\uD569\uB2C8\uB2E4.`);
|
|
5849
|
+
throw new Error("score-below-threshold");
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
);
|
|
5853
|
+
await syncHandoff(cwd).catch(() => {
|
|
5854
|
+
});
|
|
5855
|
+
} catch (err) {
|
|
5856
|
+
if (err instanceof Error && err.message === "score-below-threshold") {
|
|
5857
|
+
process.exitCode = 1;
|
|
5858
|
+
return;
|
|
5859
|
+
}
|
|
5860
|
+
handleRobocoError(err);
|
|
5861
|
+
}
|
|
5862
|
+
}
|
|
5863
|
+
var init_evaluate_cmd = __esm({
|
|
5864
|
+
"src/cli/commands/evaluate.cmd.ts"() {
|
|
5865
|
+
"use strict";
|
|
5866
|
+
init_ui();
|
|
5867
|
+
init_analyzer2();
|
|
5868
|
+
init_scorer();
|
|
5095
5869
|
init_cache();
|
|
5096
5870
|
init_reporter();
|
|
5097
5871
|
init_config();
|
|
5872
|
+
init_roboco();
|
|
5873
|
+
init_gates();
|
|
5874
|
+
init_validate_cmd();
|
|
5098
5875
|
}
|
|
5099
5876
|
});
|
|
5100
5877
|
|
|
@@ -5105,7 +5882,22 @@ __export(env_cmd_exports, {
|
|
|
5105
5882
|
envSetupCommand: () => envSetupCommand,
|
|
5106
5883
|
envStatusCommand: () => envStatusCommand
|
|
5107
5884
|
});
|
|
5108
|
-
async function envSetupCommand(cwd) {
|
|
5885
|
+
async function envSetupCommand(cwd, options = {}) {
|
|
5886
|
+
try {
|
|
5887
|
+
await withRoboco(
|
|
5888
|
+
cwd,
|
|
5889
|
+
"environment",
|
|
5890
|
+
"pai add",
|
|
5891
|
+
{ skipGates: options.skipGates, force: options.force },
|
|
5892
|
+
async () => envSetupCommandInner(cwd)
|
|
5893
|
+
);
|
|
5894
|
+
await syncHandoff(cwd).catch(() => {
|
|
5895
|
+
});
|
|
5896
|
+
} catch (err) {
|
|
5897
|
+
handleRobocoError(err);
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5900
|
+
async function envSetupCommandInner(cwd) {
|
|
5109
5901
|
const { default: inquirer } = await import("inquirer");
|
|
5110
5902
|
const { basename: basename5 } = await import("path");
|
|
5111
5903
|
section("\uD50C\uB7EC\uADF8\uC778 \uCD94\uAC00 \xB7 \uBAA8\uB4DC \uC2B9\uACA9");
|
|
@@ -5231,6 +6023,54 @@ async function envStatusCommand(cwd) {
|
|
|
5231
6023
|
hint(`\uC2B9\uACA9 \uACBD\uB85C: ${state.projectMode} \u2192 ${next} (pai add \uC5D0\uC11C \uC2B9\uACA9 \uAC00\uB2A5)`);
|
|
5232
6024
|
}
|
|
5233
6025
|
}
|
|
6026
|
+
const robo = await loadRobocoState(cwd);
|
|
6027
|
+
console.log("");
|
|
6028
|
+
console.log(colors.accent(" \uD30C\uC774\uD504\uB77C\uC778 \uC9C4\uD589 (roboco)"));
|
|
6029
|
+
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"));
|
|
6030
|
+
const stages = ["environment", "design", "execution", "validation", "evaluation"];
|
|
6031
|
+
for (const s of stages) {
|
|
6032
|
+
const latest = [...robo.stageHistory].reverse().find((h) => h.stage === s);
|
|
6033
|
+
const gate = robo.gates[`${s}.entry`];
|
|
6034
|
+
let mark = "\u25CB";
|
|
6035
|
+
let label = "\uB300\uAE30";
|
|
6036
|
+
if (latest?.status === "done") {
|
|
6037
|
+
mark = "\u2713";
|
|
6038
|
+
label = "\uC644\uB8CC";
|
|
6039
|
+
} else if (latest?.status === "in_progress") {
|
|
6040
|
+
mark = "\u25B8";
|
|
6041
|
+
label = "\uC9C4\uD589 \uC911";
|
|
6042
|
+
} else if (latest?.status === "failed") {
|
|
6043
|
+
mark = "\u2717";
|
|
6044
|
+
label = `\uC2E4\uD328 (${latest.reason ?? "\uC6D0\uC778 \uBD88\uBA85"})`;
|
|
6045
|
+
} else if (gate && !gate.passed) {
|
|
6046
|
+
mark = "\u25CB";
|
|
6047
|
+
label = `\uB300\uAE30 (\uAC8C\uC774\uD2B8 ${gate.violations.length}\uAC74 \uBBF8\uD1B5\uACFC)`;
|
|
6048
|
+
}
|
|
6049
|
+
console.log(` ${mark} ${s.padEnd(12)} ${colors.dim(label)}`);
|
|
6050
|
+
}
|
|
6051
|
+
const failedGates = Object.values(robo.gates).filter((g) => !g.passed);
|
|
6052
|
+
if (failedGates.length > 0) {
|
|
6053
|
+
console.log("");
|
|
6054
|
+
console.log(colors.accent(" \uBBF8\uD1B5\uACFC \uAC8C\uC774\uD2B8"));
|
|
6055
|
+
for (const g of failedGates) {
|
|
6056
|
+
console.log(` \u2717 ${g.name}`);
|
|
6057
|
+
for (const v of g.violations) {
|
|
6058
|
+
console.log(colors.dim(` \xB7 ${v.message}`));
|
|
6059
|
+
}
|
|
6060
|
+
}
|
|
6061
|
+
}
|
|
6062
|
+
console.log("");
|
|
6063
|
+
if (robo.lock) {
|
|
6064
|
+
const stale = isLockStale(robo.lock);
|
|
6065
|
+
const mins = Math.round((Date.now() - new Date(robo.lock.acquiredAt).getTime()) / 6e4);
|
|
6066
|
+
if (stale) {
|
|
6067
|
+
warn(`\uB77D stale (${robo.lock.command}, PID ${robo.lock.pid}) \u2014 \uB2E4\uC74C \uBA85\uB839 \uC2DC \uC790\uB3D9 \uD574\uC81C\uB428`);
|
|
6068
|
+
} else {
|
|
6069
|
+
warn(`\uC9C4\uD589 \uC911: ${robo.lock.command} (PID ${robo.lock.pid}, ${mins}\uBD84 \uC804)`);
|
|
6070
|
+
}
|
|
6071
|
+
} else {
|
|
6072
|
+
success("\uB77D \uC5C6\uC74C (\uC5B4\uB290 \uBA85\uB839\uC774\uB4E0 \uC2E4\uD589 \uAC00\uB2A5)");
|
|
6073
|
+
}
|
|
5234
6074
|
}
|
|
5235
6075
|
var init_env_cmd = __esm({
|
|
5236
6076
|
"src/cli/commands/env.cmd.ts"() {
|
|
@@ -5242,6 +6082,8 @@ var init_env_cmd = __esm({
|
|
|
5242
6082
|
init_doctor();
|
|
5243
6083
|
init_analyzer();
|
|
5244
6084
|
init_logger();
|
|
6085
|
+
init_roboco();
|
|
6086
|
+
init_validate_cmd();
|
|
5245
6087
|
}
|
|
5246
6088
|
});
|
|
5247
6089
|
|
|
@@ -5250,8 +6092,8 @@ var init_cmd_exports = {};
|
|
|
5250
6092
|
__export(init_cmd_exports, {
|
|
5251
6093
|
initCommand: () => initCommand
|
|
5252
6094
|
});
|
|
5253
|
-
import { join as
|
|
5254
|
-
import
|
|
6095
|
+
import { join as join13, basename as basename2 } from "path";
|
|
6096
|
+
import fs21 from "fs-extra";
|
|
5255
6097
|
async function initCommand(cwd, nameArg) {
|
|
5256
6098
|
printWelcomeBanner();
|
|
5257
6099
|
const { isWindows: isWindows2, diagnoseWindowsEnv: diagnoseWindowsEnv2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
|
|
@@ -5309,11 +6151,11 @@ async function initCommand(cwd, nameArg) {
|
|
|
5309
6151
|
const evalResult = computeResult2(llmOutput);
|
|
5310
6152
|
printReport2(evalResult);
|
|
5311
6153
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5312
|
-
const reportDir =
|
|
5313
|
-
await
|
|
6154
|
+
const reportDir = join13(cwd, "docs", "p-reports");
|
|
6155
|
+
await fs21.ensureDir(reportDir);
|
|
5314
6156
|
const legacyName = basename2(cwd);
|
|
5315
6157
|
const detailedReport = buildDetailedReport3(evalResult, legacyName);
|
|
5316
|
-
await
|
|
6158
|
+
await fs21.writeFile(join13(reportDir, `${today}.md`), detailedReport, "utf8");
|
|
5317
6159
|
console.log("");
|
|
5318
6160
|
success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
|
|
5319
6161
|
} catch {
|
|
@@ -5358,8 +6200,8 @@ async function initCommand(cwd, nameArg) {
|
|
|
5358
6200
|
}]);
|
|
5359
6201
|
projectName = answer.name.trim();
|
|
5360
6202
|
}
|
|
5361
|
-
const projectDir =
|
|
5362
|
-
if (await
|
|
6203
|
+
const projectDir = join13(cwd, projectName);
|
|
6204
|
+
if (await fs21.pathExists(projectDir)) {
|
|
5363
6205
|
const existingConfig = await loadConfig(projectDir);
|
|
5364
6206
|
if (existingConfig) {
|
|
5365
6207
|
console.log("");
|
|
@@ -5372,7 +6214,7 @@ async function initCommand(cwd, nameArg) {
|
|
|
5372
6214
|
warn(`${projectName}/ \uD3F4\uB354\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4.`);
|
|
5373
6215
|
hint("\uC774 \uD3F4\uB354\uC5D0 PAI\uB97C \uC124\uCE58\uD569\uB2C8\uB2E4.");
|
|
5374
6216
|
} else {
|
|
5375
|
-
await
|
|
6217
|
+
await fs21.ensureDir(projectDir);
|
|
5376
6218
|
success(`${projectName}/ \uD3F4\uB354 \uC0DD\uC131`);
|
|
5377
6219
|
}
|
|
5378
6220
|
await setupInDirectory(projectDir, projectName);
|
|
@@ -5457,7 +6299,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
|
|
|
5457
6299
|
...extraTools.includes("omc") ? [{ label: "OMC \uB7F0\uD0C0\uC784", path: ".omc" }] : []
|
|
5458
6300
|
];
|
|
5459
6301
|
for (const check of checks) {
|
|
5460
|
-
const exists = await
|
|
6302
|
+
const exists = await fs21.pathExists(join13(projectDir, check.path));
|
|
5461
6303
|
console.log(` ${exists ? colors.success("\u2713") : colors.err("\u2717")} ${check.label.padEnd(16)} ${colors.dim(check.path)}`);
|
|
5462
6304
|
}
|
|
5463
6305
|
console.log("");
|
|
@@ -5482,10 +6324,10 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
|
|
|
5482
6324
|
printReport2(evalResult);
|
|
5483
6325
|
await sleep2(500);
|
|
5484
6326
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5485
|
-
const reportDir =
|
|
5486
|
-
await
|
|
6327
|
+
const reportDir = join13(projectDir, "docs", "p-reports");
|
|
6328
|
+
await fs21.ensureDir(reportDir);
|
|
5487
6329
|
const detailedReport = buildDetailedReport3(evalResult, projectName);
|
|
5488
|
-
await
|
|
6330
|
+
await fs21.writeFile(join13(reportDir, `${today}.md`), detailedReport, "utf8");
|
|
5489
6331
|
await sleep2(500);
|
|
5490
6332
|
console.log("");
|
|
5491
6333
|
success(`\uB9AC\uD3EC\uD2B8 \uC800\uC7A5: docs/p-reports/${today}.md`);
|
|
@@ -5536,7 +6378,7 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
|
|
|
5536
6378
|
const shellRc = getShellRcPath2();
|
|
5537
6379
|
let yoloAlreadyAliased = false;
|
|
5538
6380
|
try {
|
|
5539
|
-
const rcContent = await
|
|
6381
|
+
const rcContent = await fs21.readFile(shellRc, "utf8");
|
|
5540
6382
|
yoloAlreadyAliased = checkYolo(rcContent);
|
|
5541
6383
|
} catch {
|
|
5542
6384
|
}
|
|
@@ -5556,10 +6398,10 @@ async function showCompletion(projectName, projectDir, extraTools, isCurrentDir)
|
|
|
5556
6398
|
try {
|
|
5557
6399
|
const { getYoloAliasLine: getYoloAliasLine2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
|
|
5558
6400
|
const aliasLine = getYoloAliasLine2();
|
|
5559
|
-
const rcContent = await
|
|
6401
|
+
const rcContent = await fs21.readFile(shellRc, "utf8").catch(() => "");
|
|
5560
6402
|
if (!rcContent.includes("claude-yolo")) {
|
|
5561
|
-
await
|
|
5562
|
-
await
|
|
6403
|
+
await fs21.ensureDir(join13(shellRc, ".."));
|
|
6404
|
+
await fs21.appendFile(shellRc, `
|
|
5563
6405
|
# PAI \u2014 claude-YOLO mode
|
|
5564
6406
|
${aliasLine}
|
|
5565
6407
|
`);
|
|
@@ -5752,9 +6594,9 @@ async function installOrchestratorOnly(projectDir, projectName) {
|
|
|
5752
6594
|
const evalResult = computeResult2(llmOutput);
|
|
5753
6595
|
printReport2(evalResult);
|
|
5754
6596
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5755
|
-
const reportDir =
|
|
5756
|
-
await
|
|
5757
|
-
await
|
|
6597
|
+
const reportDir = join13(projectDir, "docs", "p-reports");
|
|
6598
|
+
await fs21.ensureDir(reportDir);
|
|
6599
|
+
await fs21.writeFile(join13(reportDir, `${today}.md`), buildDetailedReport3(evalResult, projectName), "utf8");
|
|
5758
6600
|
console.log("");
|
|
5759
6601
|
hint(`\uC0C1\uC138 \uB9AC\uD3EC\uD2B8: docs/p-reports/${today}.md`);
|
|
5760
6602
|
} catch {
|
|
@@ -5804,7 +6646,7 @@ async function detectLegacyProject(cwd) {
|
|
|
5804
6646
|
".gitignore"
|
|
5805
6647
|
];
|
|
5806
6648
|
for (const signal of signals) {
|
|
5807
|
-
if (await
|
|
6649
|
+
if (await fs21.pathExists(join13(cwd, signal))) return true;
|
|
5808
6650
|
}
|
|
5809
6651
|
return false;
|
|
5810
6652
|
}
|
|
@@ -5895,17 +6737,17 @@ var init_help_cmd = __esm({
|
|
|
5895
6737
|
});
|
|
5896
6738
|
|
|
5897
6739
|
// src/stages/design/openspec.ts
|
|
5898
|
-
import { join as
|
|
5899
|
-
import
|
|
6740
|
+
import { join as join14 } from "path";
|
|
6741
|
+
import fs22 from "fs-extra";
|
|
5900
6742
|
async function initOpenSpec(cwd, projectName) {
|
|
5901
|
-
const docsDir =
|
|
5902
|
-
await
|
|
5903
|
-
const openspecPath =
|
|
5904
|
-
if (await
|
|
6743
|
+
const docsDir = join14(cwd, "docs");
|
|
6744
|
+
await fs22.ensureDir(docsDir);
|
|
6745
|
+
const openspecPath = join14(docsDir, "openspec.md");
|
|
6746
|
+
if (await fs22.pathExists(openspecPath)) {
|
|
5905
6747
|
info("docs/openspec.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
|
|
5906
6748
|
return;
|
|
5907
6749
|
}
|
|
5908
|
-
await
|
|
6750
|
+
await fs22.writeFile(openspecPath, [
|
|
5909
6751
|
`# OpenSpec \u2014 ${projectName}`,
|
|
5910
6752
|
"",
|
|
5911
6753
|
"## 1. \uBAA9\uC801 (Purpose)",
|
|
@@ -5933,13 +6775,13 @@ async function initOpenSpec(cwd, projectName) {
|
|
|
5933
6775
|
}
|
|
5934
6776
|
async function validateOpenSpec(cwd) {
|
|
5935
6777
|
const candidates = [
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
6778
|
+
join14(cwd, "docs", "openspec.md"),
|
|
6779
|
+
join14(cwd, "openspec.md"),
|
|
6780
|
+
join14(cwd, ".pai", "openspec.md")
|
|
5939
6781
|
];
|
|
5940
6782
|
let specPath = null;
|
|
5941
6783
|
for (const p of candidates) {
|
|
5942
|
-
if (await
|
|
6784
|
+
if (await fs22.pathExists(p)) {
|
|
5943
6785
|
specPath = p;
|
|
5944
6786
|
break;
|
|
5945
6787
|
}
|
|
@@ -5953,7 +6795,7 @@ async function validateOpenSpec(cwd) {
|
|
|
5953
6795
|
warnings: ["openspec.md \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `pai design init` \uC744 \uC2E4\uD589\uD558\uC138\uC694."]
|
|
5954
6796
|
};
|
|
5955
6797
|
}
|
|
5956
|
-
const content = await
|
|
6798
|
+
const content = await fs22.readFile(specPath, "utf8");
|
|
5957
6799
|
const missing = [];
|
|
5958
6800
|
let filled = 0;
|
|
5959
6801
|
for (const section2 of REQUIRED_SECTIONS) {
|
|
@@ -6001,17 +6843,17 @@ var init_openspec = __esm({
|
|
|
6001
6843
|
});
|
|
6002
6844
|
|
|
6003
6845
|
// src/stages/design/omc.ts
|
|
6004
|
-
import { join as
|
|
6005
|
-
import
|
|
6846
|
+
import { join as join15 } from "path";
|
|
6847
|
+
import fs23 from "fs-extra";
|
|
6006
6848
|
async function initOMC(cwd, projectName) {
|
|
6007
|
-
const paiDir =
|
|
6008
|
-
await
|
|
6009
|
-
const omcPath =
|
|
6010
|
-
if (await
|
|
6849
|
+
const paiDir = join15(cwd, ".pai");
|
|
6850
|
+
await fs23.ensureDir(paiDir);
|
|
6851
|
+
const omcPath = join15(paiDir, "omc.md");
|
|
6852
|
+
if (await fs23.pathExists(omcPath)) {
|
|
6011
6853
|
info(".pai/omc.md \uC774\uBBF8 \uC874\uC7AC \u2014 \uAC74\uB108\uB700");
|
|
6012
6854
|
return;
|
|
6013
6855
|
}
|
|
6014
|
-
await
|
|
6856
|
+
await fs23.writeFile(omcPath, [
|
|
6015
6857
|
`# OMC \u2014 Object Model Context (${projectName})`,
|
|
6016
6858
|
"",
|
|
6017
6859
|
"> AI\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uB3C4\uBA54\uC778\uC744 \uC774\uD574\uD558\uAE30 \uC704\uD55C \uD575\uC2EC \uAC1D\uCCB4 \uBAA8\uB378",
|
|
@@ -6051,195 +6893,71 @@ __export(design_cmd_exports, {
|
|
|
6051
6893
|
designValidateCommand: () => designValidateCommand
|
|
6052
6894
|
});
|
|
6053
6895
|
import { basename as basename3 } from "path";
|
|
6054
|
-
async function designInitCommand(cwd) {
|
|
6055
|
-
section("\uC124\uACC4 \uD15C\uD50C\uB9BF \uC0DD\uC131");
|
|
6056
|
-
const config = await loadConfig(cwd);
|
|
6057
|
-
const projectName = config?.projectName ?? basename3(cwd);
|
|
6058
|
-
await initOpenSpec(cwd, projectName);
|
|
6059
|
-
await initOMC(cwd, projectName);
|
|
6060
|
-
info("");
|
|
6061
|
-
info("\uB2E4\uC74C \uB2E8\uACC4: docs/openspec.md\uB97C \uC5F4\uC5B4 PRD\uB97C \uC791\uC131\uD558\uC138\uC694.");
|
|
6062
|
-
}
|
|
6063
|
-
async function designValidateCommand(cwd) {
|
|
6064
|
-
section("PRD \uC644\uC131\uB3C4 \uAC80\uC99D");
|
|
6065
|
-
const result = await validateOpenSpec(cwd);
|
|
6066
|
-
info(`\uC139\uC158 \uC644\uC131\uB3C4: ${result.filledSections}/${result.totalSections}`);
|
|
6067
|
-
if (result.missing.length > 0) {
|
|
6068
|
-
console.log("");
|
|
6069
|
-
warn("\uBBF8\uC791\uC131 \uC139\uC158:");
|
|
6070
|
-
for (const m of result.missing) {
|
|
6071
|
-
warn(` - ${m}`);
|
|
6072
|
-
}
|
|
6073
|
-
}
|
|
6074
|
-
for (const w of result.warnings) {
|
|
6075
|
-
warn(w);
|
|
6076
|
-
}
|
|
6077
|
-
if (result.complete) {
|
|
6078
|
-
console.log("");
|
|
6079
|
-
success("PRD \uC644\uC131\uB3C4 \uAC80\uC99D \uD1B5\uACFC!");
|
|
6080
|
-
} else {
|
|
6081
|
-
console.log("");
|
|
6082
|
-
info("docs/openspec.md\uB97C \uC5F4\uC5B4 \uBBF8\uC791\uC131 \uC139\uC158\uC744 \uCC44\uC6CC\uC8FC\uC138\uC694.");
|
|
6083
|
-
}
|
|
6084
|
-
}
|
|
6085
|
-
var init_design_cmd = __esm({
|
|
6086
|
-
"src/cli/commands/design.cmd.ts"() {
|
|
6087
|
-
"use strict";
|
|
6088
|
-
init_ui();
|
|
6089
|
-
init_openspec();
|
|
6090
|
-
init_omc();
|
|
6091
|
-
init_config();
|
|
6092
|
-
}
|
|
6093
|
-
});
|
|
6094
|
-
|
|
6095
|
-
// src/stages/validation/runner.ts
|
|
6096
|
-
import { join as join14 } from "path";
|
|
6097
|
-
import fs20 from "fs-extra";
|
|
6098
|
-
async function runTests(cwd) {
|
|
6099
|
-
const start = Date.now();
|
|
6100
|
-
const gstackPath = join14(cwd, ".pai", "gstack.json");
|
|
6101
|
-
let runner = "npm test";
|
|
6102
|
-
if (await fs20.pathExists(gstackPath)) {
|
|
6103
|
-
try {
|
|
6104
|
-
const config = await fs20.readJson(gstackPath);
|
|
6105
|
-
if (config.testRunner === "vitest") runner = "npx vitest run";
|
|
6106
|
-
else if (config.testRunner === "jest") runner = "npx jest";
|
|
6107
|
-
else if (config.testRunner === "mocha") runner = "npx mocha";
|
|
6108
|
-
} catch {
|
|
6109
|
-
}
|
|
6110
|
-
}
|
|
6111
|
-
const pkgPath = join14(cwd, "package.json");
|
|
6112
|
-
if (await fs20.pathExists(pkgPath)) {
|
|
6113
|
-
try {
|
|
6114
|
-
const pkg5 = await fs20.readJson(pkgPath);
|
|
6115
|
-
if (!pkg5.scripts?.test || pkg5.scripts.test.includes("no test specified")) {
|
|
6116
|
-
return {
|
|
6117
|
-
runner,
|
|
6118
|
-
passed: false,
|
|
6119
|
-
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.",
|
|
6120
|
-
duration: Date.now() - start
|
|
6121
|
-
};
|
|
6122
|
-
}
|
|
6123
|
-
} catch {
|
|
6124
|
-
}
|
|
6125
|
-
}
|
|
6896
|
+
async function designInitCommand(cwd, options = {}) {
|
|
6126
6897
|
try {
|
|
6127
|
-
|
|
6128
|
-
const { stdout, stderr } = await execa("npm", ["test"], {
|
|
6898
|
+
await withRoboco(
|
|
6129
6899
|
cwd,
|
|
6130
|
-
|
|
6131
|
-
|
|
6900
|
+
"design",
|
|
6901
|
+
"pai design init",
|
|
6902
|
+
{ skipGates: options.skipGates, force: options.force, gate: STAGE_GATES.design },
|
|
6903
|
+
async () => {
|
|
6904
|
+
section("\uC124\uACC4 \uD15C\uD50C\uB9BF \uC0DD\uC131");
|
|
6905
|
+
const config = await loadConfig(cwd);
|
|
6906
|
+
const projectName = config?.projectName ?? basename3(cwd);
|
|
6907
|
+
await initOpenSpec(cwd, projectName);
|
|
6908
|
+
await initOMC(cwd, projectName);
|
|
6909
|
+
info("");
|
|
6910
|
+
info("\uB2E4\uC74C \uB2E8\uACC4: docs/openspec.md\uB97C \uC5F4\uC5B4 PRD\uB97C \uC791\uC131\uD558\uC138\uC694.");
|
|
6911
|
+
}
|
|
6912
|
+
);
|
|
6913
|
+
await syncHandoff(cwd).catch(() => {
|
|
6132
6914
|
});
|
|
6133
|
-
return {
|
|
6134
|
-
runner,
|
|
6135
|
-
passed: true,
|
|
6136
|
-
output: stdout || stderr,
|
|
6137
|
-
duration: Date.now() - start
|
|
6138
|
-
};
|
|
6139
6915
|
} catch (err) {
|
|
6140
|
-
|
|
6141
|
-
return {
|
|
6142
|
-
runner,
|
|
6143
|
-
passed: false,
|
|
6144
|
-
output,
|
|
6145
|
-
duration: Date.now() - start
|
|
6146
|
-
};
|
|
6916
|
+
handleRobocoError(err);
|
|
6147
6917
|
}
|
|
6148
6918
|
}
|
|
6149
|
-
|
|
6150
|
-
"src/stages/validation/runner.ts"() {
|
|
6151
|
-
"use strict";
|
|
6152
|
-
}
|
|
6153
|
-
});
|
|
6154
|
-
|
|
6155
|
-
// src/stages/validation/harness.ts
|
|
6156
|
-
import { join as join15 } from "path";
|
|
6157
|
-
import fs21 from "fs-extra";
|
|
6158
|
-
async function runHarnessCheck(cwd) {
|
|
6159
|
-
const harnessPath = join15(cwd, ".pai", "harness.json");
|
|
6160
|
-
if (!await fs21.pathExists(harnessPath)) {
|
|
6161
|
-
return { enabled: false, specFile: null, rules: [], checks: [] };
|
|
6162
|
-
}
|
|
6163
|
-
let config;
|
|
6919
|
+
async function designValidateCommand(cwd, options = {}) {
|
|
6164
6920
|
try {
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
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"
|
|
6188
|
-
});
|
|
6189
|
-
}
|
|
6190
|
-
return { enabled: true, specFile, rules, checks };
|
|
6191
|
-
}
|
|
6192
|
-
var init_harness = __esm({
|
|
6193
|
-
"src/stages/validation/harness.ts"() {
|
|
6194
|
-
"use strict";
|
|
6195
|
-
}
|
|
6196
|
-
});
|
|
6197
|
-
|
|
6198
|
-
// src/cli/commands/validate.cmd.ts
|
|
6199
|
-
var validate_cmd_exports = {};
|
|
6200
|
-
__export(validate_cmd_exports, {
|
|
6201
|
-
validateCommand: () => validateCommand
|
|
6202
|
-
});
|
|
6203
|
-
async function validateCommand(cwd) {
|
|
6204
|
-
section("\uD14C\uC2A4\uD2B8 \uC2E4\uD589");
|
|
6205
|
-
const testResult = await runTests(cwd);
|
|
6206
|
-
if (testResult.passed) {
|
|
6207
|
-
success(`\uD14C\uC2A4\uD2B8 \uD1B5\uACFC (${testResult.runner}, ${testResult.duration}ms)`);
|
|
6208
|
-
} else {
|
|
6209
|
-
error("\uD14C\uC2A4\uD2B8 \uC2E4\uD328");
|
|
6210
|
-
info(testResult.output.slice(0, 300));
|
|
6211
|
-
}
|
|
6212
|
-
section("\uD558\uB124\uC2A4 \uAC80\uC99D");
|
|
6213
|
-
const harness = await runHarnessCheck(cwd);
|
|
6214
|
-
if (!harness.enabled) {
|
|
6215
|
-
info("Harness \uC124\uC815 \uC5C6\uC74C \u2014 \uAC74\uB108\uB700");
|
|
6216
|
-
info("\uC124\uC815 \uCD94\uAC00: `pai env setup` \uC5D0\uC11C Harness Engineering \uC120\uD0DD");
|
|
6217
|
-
} else {
|
|
6218
|
-
for (const check of harness.checks) {
|
|
6219
|
-
if (check.passed) {
|
|
6220
|
-
success(`${check.rule}: ${check.detail}`);
|
|
6221
|
-
} else {
|
|
6222
|
-
warn(`${check.rule}: ${check.detail}`);
|
|
6921
|
+
await withRoboco(
|
|
6922
|
+
cwd,
|
|
6923
|
+
"design",
|
|
6924
|
+
"pai design validate",
|
|
6925
|
+
{ skipGates: options.skipGates, force: options.force, gate: STAGE_GATES.design },
|
|
6926
|
+
async () => {
|
|
6927
|
+
section("PRD \uC644\uC131\uB3C4 \uAC80\uC99D");
|
|
6928
|
+
const result = await validateOpenSpec(cwd);
|
|
6929
|
+
info(`\uC139\uC158 \uC644\uC131\uB3C4: ${result.filledSections}/${result.totalSections}`);
|
|
6930
|
+
if (result.missing.length > 0) {
|
|
6931
|
+
console.log("");
|
|
6932
|
+
warn("\uBBF8\uC791\uC131 \uC139\uC158:");
|
|
6933
|
+
for (const m of result.missing) warn(` - ${m}`);
|
|
6934
|
+
}
|
|
6935
|
+
for (const w of result.warnings) warn(w);
|
|
6936
|
+
if (result.complete) {
|
|
6937
|
+
console.log("");
|
|
6938
|
+
success("PRD \uC644\uC131\uB3C4 \uAC80\uC99D \uD1B5\uACFC!");
|
|
6939
|
+
} else {
|
|
6940
|
+
console.log("");
|
|
6941
|
+
info("docs/openspec.md\uB97C \uC5F4\uC5B4 \uBBF8\uC791\uC131 \uC139\uC158\uC744 \uCC44\uC6CC\uC8FC\uC138\uC694.");
|
|
6942
|
+
}
|
|
6223
6943
|
}
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
success("\uAC80\uC99D \uD1B5\uACFC!");
|
|
6230
|
-
} else if (!testResult.passed) {
|
|
6231
|
-
error("\uAC80\uC99D \uC2E4\uD328 \u2014 \uD14C\uC2A4\uD2B8\uB97C \uC218\uC815\uD558\uC138\uC694.");
|
|
6232
|
-
process.exitCode = 1;
|
|
6233
|
-
} else {
|
|
6234
|
-
warn("\uBD80\uBD84 \uD1B5\uACFC \u2014 \uD558\uB124\uC2A4 \uAC80\uC99D \uD56D\uBAA9\uC744 \uD655\uC778\uD558\uC138\uC694.");
|
|
6944
|
+
);
|
|
6945
|
+
await syncHandoff(cwd).catch(() => {
|
|
6946
|
+
});
|
|
6947
|
+
} catch (err) {
|
|
6948
|
+
handleRobocoError(err);
|
|
6235
6949
|
}
|
|
6236
6950
|
}
|
|
6237
|
-
var
|
|
6238
|
-
"src/cli/commands/
|
|
6951
|
+
var init_design_cmd = __esm({
|
|
6952
|
+
"src/cli/commands/design.cmd.ts"() {
|
|
6239
6953
|
"use strict";
|
|
6240
6954
|
init_ui();
|
|
6241
|
-
|
|
6242
|
-
|
|
6955
|
+
init_openspec();
|
|
6956
|
+
init_omc();
|
|
6957
|
+
init_config();
|
|
6958
|
+
init_roboco();
|
|
6959
|
+
init_gates();
|
|
6960
|
+
init_validate_cmd();
|
|
6243
6961
|
}
|
|
6244
6962
|
});
|
|
6245
6963
|
|
|
@@ -6295,13 +7013,13 @@ var init_context = __esm({
|
|
|
6295
7013
|
|
|
6296
7014
|
// src/stages/design/index.ts
|
|
6297
7015
|
import { join as join16 } from "path";
|
|
6298
|
-
import
|
|
7016
|
+
import fs24 from "fs-extra";
|
|
6299
7017
|
async function autoInstallHarness(cwd) {
|
|
6300
7018
|
const harnessPath = join16(cwd, ".pai", "harness.json");
|
|
6301
|
-
if (await
|
|
7019
|
+
if (await fs24.pathExists(harnessPath)) return;
|
|
6302
7020
|
await withSpinner("Harness Engineering \uC790\uB3D9 \uC124\uC815 \uC911...", async () => {
|
|
6303
|
-
await
|
|
6304
|
-
await
|
|
7021
|
+
await fs24.ensureDir(join16(cwd, ".pai"));
|
|
7022
|
+
await fs24.writeJson(harnessPath, {
|
|
6305
7023
|
version: "1.0",
|
|
6306
7024
|
specFile: "docs/openspec.md",
|
|
6307
7025
|
checkOn: ["pre-commit", "ci"],
|
|
@@ -6621,22 +7339,41 @@ __export(pipeline_cmd_exports, {
|
|
|
6621
7339
|
});
|
|
6622
7340
|
async function pipelineCommand(cwd, options) {
|
|
6623
7341
|
printBanner();
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
7342
|
+
try {
|
|
7343
|
+
await withRoboco(
|
|
7344
|
+
cwd,
|
|
7345
|
+
"environment",
|
|
7346
|
+
"pai run",
|
|
7347
|
+
{
|
|
7348
|
+
skipGates: options.skipGates,
|
|
7349
|
+
force: options.force,
|
|
7350
|
+
ttlMs: PIPELINE_TTL_MS
|
|
7351
|
+
},
|
|
7352
|
+
async () => {
|
|
7353
|
+
const config = await loadConfig(cwd) ?? createDefaultConfig("my-project", "prototype");
|
|
7354
|
+
const pipelineOpts = {};
|
|
7355
|
+
if (options.from) pipelineOpts.from = options.from;
|
|
7356
|
+
if (options.only) pipelineOpts.only = options.only.split(",").map((s) => s.trim());
|
|
7357
|
+
await runPipeline(cwd, config, pipelineOpts);
|
|
7358
|
+
}
|
|
7359
|
+
);
|
|
7360
|
+
await syncHandoff(cwd).catch(() => {
|
|
7361
|
+
});
|
|
7362
|
+
} catch (err) {
|
|
7363
|
+
handleRobocoError(err);
|
|
6631
7364
|
}
|
|
6632
|
-
await runPipeline(cwd, config, pipelineOpts);
|
|
6633
7365
|
}
|
|
7366
|
+
var PIPELINE_TTL_MS;
|
|
6634
7367
|
var init_pipeline_cmd = __esm({
|
|
6635
7368
|
"src/cli/commands/pipeline.cmd.ts"() {
|
|
6636
7369
|
"use strict";
|
|
7370
|
+
init_ui();
|
|
6637
7371
|
init_pipeline();
|
|
6638
7372
|
init_config();
|
|
6639
7373
|
init_ui();
|
|
7374
|
+
init_roboco();
|
|
7375
|
+
init_validate_cmd();
|
|
7376
|
+
PIPELINE_TTL_MS = 30 * 60 * 1e3;
|
|
6640
7377
|
}
|
|
6641
7378
|
});
|
|
6642
7379
|
|
|
@@ -6658,7 +7395,7 @@ __export(remove_cmd_exports, {
|
|
|
6658
7395
|
removeCommand: () => removeCommand
|
|
6659
7396
|
});
|
|
6660
7397
|
import { basename as basename4, dirname } from "path";
|
|
6661
|
-
import
|
|
7398
|
+
import fs25 from "fs-extra";
|
|
6662
7399
|
async function removeCommand(cwd, options) {
|
|
6663
7400
|
section("\uD504\uB85C\uC81D\uD2B8 \uC0AD\uC81C");
|
|
6664
7401
|
const config = await loadConfig(cwd);
|
|
@@ -6676,7 +7413,7 @@ async function removeCommand(cwd, options) {
|
|
|
6676
7413
|
console.log(colors.err(` ${folderName}/ \uD3F4\uB354 \uC804\uCCB4\uAC00 \uC0AD\uC81C\uB429\uB2C8\uB2E4.`));
|
|
6677
7414
|
hint("\uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
6678
7415
|
console.log("");
|
|
6679
|
-
const items = await
|
|
7416
|
+
const items = await fs25.readdir(cwd);
|
|
6680
7417
|
const fileCount = items.filter((i) => !i.startsWith(".")).length;
|
|
6681
7418
|
const hiddenCount = items.filter((i) => i.startsWith(".")).length;
|
|
6682
7419
|
info(`\uD30C\uC77C/\uD3F4\uB354 ${fileCount}\uAC1C, \uC228\uAE40 \uD56D\uBAA9 ${hiddenCount}\uAC1C`);
|
|
@@ -6695,7 +7432,7 @@ async function removeCommand(cwd, options) {
|
|
|
6695
7432
|
}
|
|
6696
7433
|
process.chdir(parentDir);
|
|
6697
7434
|
try {
|
|
6698
|
-
await
|
|
7435
|
+
await fs25.remove(cwd);
|
|
6699
7436
|
console.log("");
|
|
6700
7437
|
success(`${folderName}/ \uD504\uB85C\uC81D\uD2B8\uAC00 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
|
|
6701
7438
|
try {
|
|
@@ -6974,7 +7711,7 @@ __export(savetoken_cmd_exports, {
|
|
|
6974
7711
|
savetokenCommand: () => savetokenCommand
|
|
6975
7712
|
});
|
|
6976
7713
|
import { join as join17, relative } from "path";
|
|
6977
|
-
import
|
|
7714
|
+
import fs26 from "fs-extra";
|
|
6978
7715
|
import chalk7 from "chalk";
|
|
6979
7716
|
async function savetokenCommand(cwd) {
|
|
6980
7717
|
const { createSpinner: createSpinner2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
|
|
@@ -7044,10 +7781,10 @@ async function savetokenCommand(cwd) {
|
|
|
7044
7781
|
console.log(` ${chalk7.red("\u25CF")} \uB192\uC74C \uCF54\uB4DC \uC0DD\uC131, \uBCF5\uC7A1\uD55C \uCD94\uB860, \uCC3D\uC758\uC801 \uC0DD\uC131`);
|
|
7045
7782
|
console.log(` \u2192 ${colors.dim("AI \uD544\uC218 \u2014 \uD504\uB86C\uD504\uD2B8 \uCD5C\uC801\uD654\uB85C \uD1A0\uD070 \uC808\uAC10")}`);
|
|
7046
7783
|
const reportDir = join17(cwd, ".pai");
|
|
7047
|
-
await
|
|
7784
|
+
await fs26.ensureDir(reportDir);
|
|
7048
7785
|
const report = buildReport(callSites, cwd);
|
|
7049
7786
|
const reportPath = join17(reportDir, "savetoken-report.md");
|
|
7050
|
-
await
|
|
7787
|
+
await fs26.writeFile(reportPath, report, "utf8");
|
|
7051
7788
|
console.log("");
|
|
7052
7789
|
success("\uC2A4\uCE94 \uB9AC\uD3EC\uD2B8 \uC800\uC7A5: .pai/savetoken-report.md");
|
|
7053
7790
|
console.log("");
|
|
@@ -7205,7 +7942,7 @@ __export(wakeup_cmd_exports, {
|
|
|
7205
7942
|
});
|
|
7206
7943
|
import { join as join18 } from "path";
|
|
7207
7944
|
import { homedir as homedir3, platform as osPlatform } from "os";
|
|
7208
|
-
import
|
|
7945
|
+
import fs27 from "fs-extra";
|
|
7209
7946
|
import chalk8 from "chalk";
|
|
7210
7947
|
async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
|
|
7211
7948
|
if (timeOrAction === "off") {
|
|
@@ -7252,9 +7989,9 @@ async function wakeupCommand(timeOrAction, schedule = "\uD3C9\uC77C") {
|
|
|
7252
7989
|
projectDir,
|
|
7253
7990
|
launchMode
|
|
7254
7991
|
};
|
|
7255
|
-
await
|
|
7256
|
-
await
|
|
7257
|
-
await
|
|
7992
|
+
await fs27.ensureDir(PAI_DIR2);
|
|
7993
|
+
await fs27.writeJson(CONFIG_FILE2, config, { spaces: 2 });
|
|
7994
|
+
await fs27.writeJson(MESSAGES_FILE, MESSAGES);
|
|
7258
7995
|
await createWakeupScript(config);
|
|
7259
7996
|
if (osPlatform() === "darwin") {
|
|
7260
7997
|
await setupMacOS(config);
|
|
@@ -7285,7 +8022,7 @@ async function setupMacOS(config) {
|
|
|
7285
8022
|
const { execa } = await import("execa");
|
|
7286
8023
|
const [hour, minute] = config.time.split(":").map(Number);
|
|
7287
8024
|
const plistDir = join18(homedir3(), "Library", "LaunchAgents");
|
|
7288
|
-
await
|
|
8025
|
+
await fs27.ensureDir(plistDir);
|
|
7289
8026
|
const weekdays = scheduleToWeekdays(config.schedule);
|
|
7290
8027
|
let calendarEntries;
|
|
7291
8028
|
if (weekdays.length === 7) {
|
|
@@ -7321,7 +8058,7 @@ ${calendarEntries}
|
|
|
7321
8058
|
<string>${PAI_DIR2}/wakeup.log</string>
|
|
7322
8059
|
</dict>
|
|
7323
8060
|
</plist>`;
|
|
7324
|
-
await
|
|
8061
|
+
await fs27.writeFile(PLIST_PATH, plist);
|
|
7325
8062
|
await execa("launchctl", ["unload", PLIST_PATH]).catch(() => {
|
|
7326
8063
|
});
|
|
7327
8064
|
await execa("launchctl", ["load", PLIST_PATH]);
|
|
@@ -7344,7 +8081,7 @@ async function setupWindows(config) {
|
|
|
7344
8081
|
const { execa } = await import("execa");
|
|
7345
8082
|
const [hour, minute] = config.time.split(":").map(Number);
|
|
7346
8083
|
const psScriptDir = join18(homedir3(), ".pai");
|
|
7347
|
-
await
|
|
8084
|
+
await fs27.ensureDir(psScriptDir);
|
|
7348
8085
|
const psScriptPath = join18(psScriptDir, "wakeup.ps1");
|
|
7349
8086
|
const claudeCmd = config.launchMode === "yolo" ? "claude --dangerously-skip-permissions" : "claude";
|
|
7350
8087
|
const psScript = `# PAI Wakeup \u2014 Claude Code \uC138\uC158 \uC790\uB3D9 \uC2DC\uC791
|
|
@@ -7374,7 +8111,7 @@ $notifier.Show([Windows.UI.Notifications.ToastNotification]::new($xml))
|
|
|
7374
8111
|
# Open PowerShell with Claude Code
|
|
7375
8112
|
Start-Process powershell -ArgumentList "-NoExit", "-Command", "Get-Content '$todayFile'; Write-Host ''; Set-Location '${config.projectDir}'; ${claudeCmd}"
|
|
7376
8113
|
`;
|
|
7377
|
-
await
|
|
8114
|
+
await fs27.writeFile(psScriptPath, psScript, "utf8");
|
|
7378
8115
|
const daysMap = {
|
|
7379
8116
|
"\uD3C9\uC77C": "MON,TUE,WED,THU,FRI",
|
|
7380
8117
|
"\uB9E4\uC77C": "MON,TUE,WED,THU,FRI,SAT,SUN",
|
|
@@ -7422,7 +8159,7 @@ async function disableWakeup() {
|
|
|
7422
8159
|
if (osPlatform() === "darwin") {
|
|
7423
8160
|
await execa("launchctl", ["unload", PLIST_PATH]).catch(() => {
|
|
7424
8161
|
});
|
|
7425
|
-
await
|
|
8162
|
+
await fs27.remove(PLIST_PATH).catch(() => {
|
|
7426
8163
|
});
|
|
7427
8164
|
success("launchd \uC2A4\uCF00\uC904 \uC81C\uAC70");
|
|
7428
8165
|
console.log("");
|
|
@@ -7443,14 +8180,14 @@ async function disableWakeup() {
|
|
|
7443
8180
|
} else {
|
|
7444
8181
|
await removeCronEntry();
|
|
7445
8182
|
}
|
|
7446
|
-
await
|
|
8183
|
+
await fs27.remove(CONFIG_FILE2).catch(() => {
|
|
7447
8184
|
});
|
|
7448
8185
|
console.log("");
|
|
7449
8186
|
success("\u2600\uFE0F \uC6E8\uC774\uD06C\uC5C5 \uD574\uC81C \uC644\uB8CC");
|
|
7450
8187
|
}
|
|
7451
8188
|
async function showStatus() {
|
|
7452
|
-
if (await
|
|
7453
|
-
const config = await
|
|
8189
|
+
if (await fs27.pathExists(CONFIG_FILE2)) {
|
|
8190
|
+
const config = await fs27.readJson(CONFIG_FILE2);
|
|
7454
8191
|
console.log("");
|
|
7455
8192
|
success("\u2600\uFE0F \uC6E8\uC774\uD06C\uC5C5 \uD65C\uC131\uD654");
|
|
7456
8193
|
console.log(` \uC2DC\uAC04 ${chalk8.white(config.time)}`);
|
|
@@ -7458,7 +8195,7 @@ async function showStatus() {
|
|
|
7458
8195
|
console.log(` \uD504\uB85C\uC81D\uD2B8 ${chalk8.white(config.projectDir)}`);
|
|
7459
8196
|
console.log(` \uBAA8\uB4DC ${chalk8.white(config.launchMode === "yolo" ? "claude-YOLO mode" : "\uC77C\uBC18 \uBAA8\uB4DC")}`);
|
|
7460
8197
|
if (osPlatform() === "darwin") {
|
|
7461
|
-
const plistExists = await
|
|
8198
|
+
const plistExists = await fs27.pathExists(PLIST_PATH);
|
|
7462
8199
|
console.log(` launchd ${plistExists ? chalk8.green("\uD65C\uC131") : chalk8.red("\uBE44\uD65C\uC131")}`);
|
|
7463
8200
|
}
|
|
7464
8201
|
console.log("");
|
|
@@ -7611,7 +8348,7 @@ fi
|
|
|
7611
8348
|
|
|
7612
8349
|
echo "[$(date)] PAI Wakeup completed" >> "$LOG_FILE"
|
|
7613
8350
|
`;
|
|
7614
|
-
await
|
|
8351
|
+
await fs27.writeFile(SCRIPT_FILE, script, { mode: 493 });
|
|
7615
8352
|
}
|
|
7616
8353
|
var PAI_DIR2, CONFIG_FILE2, MESSAGES_FILE, SCRIPT_FILE, PLIST_NAME, PLIST_PATH, CRON_MARKER, MESSAGES;
|
|
7617
8354
|
var init_wakeup_cmd = __esm({
|
|
@@ -7711,9 +8448,9 @@ function createProgram() {
|
|
|
7711
8448
|
const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init_cmd(), init_cmd_exports));
|
|
7712
8449
|
await initCommand2(process.cwd(), projectName);
|
|
7713
8450
|
});
|
|
7714
|
-
program2.command("add").description("\uD50C\uB7EC\uADF8\uC778 \uCD94\uAC00 (Mockup \u2192 Production \uD655\uC7A5)").action(async () => {
|
|
8451
|
+
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) => {
|
|
7715
8452
|
const { addCommand } = await Promise.resolve().then(() => (init_add_cmd(), add_cmd_exports));
|
|
7716
|
-
await addCommand(process.cwd());
|
|
8453
|
+
await addCommand(process.cwd(), options);
|
|
7717
8454
|
});
|
|
7718
8455
|
program2.command("check").description("\uD658\uACBD \uC810\uAC80 (Node / Git / Claude Code)").action(async () => {
|
|
7719
8456
|
const { checkCommand: checkCommand2 } = await Promise.resolve().then(() => (init_check_cmd(), check_cmd_exports));
|
|
@@ -7728,23 +8465,23 @@ function createProgram() {
|
|
|
7728
8465
|
await helpCommand2();
|
|
7729
8466
|
});
|
|
7730
8467
|
const design = program2.command("design").description("\uC124\uACC4 \uAD00\uB9AC (OpenSpec / OMC)");
|
|
7731
|
-
design.command("init").description("PRD/OMC \uD15C\uD50C\uB9BF \uC0DD\uC131").action(async () => {
|
|
8468
|
+
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) => {
|
|
7732
8469
|
const { designInitCommand: designInitCommand2 } = await Promise.resolve().then(() => (init_design_cmd(), design_cmd_exports));
|
|
7733
|
-
await designInitCommand2(process.cwd());
|
|
8470
|
+
await designInitCommand2(process.cwd(), options);
|
|
7734
8471
|
});
|
|
7735
|
-
design.command("validate").description("PRD \uC644\uC131\uB3C4 \uAC80\uC99D").action(async () => {
|
|
8472
|
+
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) => {
|
|
7736
8473
|
const { designValidateCommand: designValidateCommand2 } = await Promise.resolve().then(() => (init_design_cmd(), design_cmd_exports));
|
|
7737
|
-
await designValidateCommand2(process.cwd());
|
|
8474
|
+
await designValidateCommand2(process.cwd(), options);
|
|
7738
8475
|
});
|
|
7739
|
-
program2.command("test").description("\uD14C\uC2A4\uD2B8 / \uD558\uB124\uC2A4 \uAC80\uC99D \uC2E4\uD589").action(async () => {
|
|
8476
|
+
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) => {
|
|
7740
8477
|
const { testCommand } = await Promise.resolve().then(() => (init_test_cmd(), test_cmd_exports));
|
|
7741
|
-
await testCommand(process.cwd());
|
|
8478
|
+
await testCommand(process.cwd(), options);
|
|
7742
8479
|
});
|
|
7743
|
-
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) => {
|
|
8480
|
+
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) => {
|
|
7744
8481
|
const { gradeCommand } = await Promise.resolve().then(() => (init_grade_cmd(), grade_cmd_exports));
|
|
7745
8482
|
await gradeCommand(process.cwd(), options);
|
|
7746
8483
|
});
|
|
7747
|
-
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) => {
|
|
8484
|
+
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) => {
|
|
7748
8485
|
const { runCommand } = await Promise.resolve().then(() => (init_run_cmd(), run_cmd_exports));
|
|
7749
8486
|
await runCommand(process.cwd(), options);
|
|
7750
8487
|
});
|