pai-zero 0.13.1 → 0.13.2
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 +297 -253
- package/dist/bin/pai.js.map +1 -1
- package/dist/cli/index.js +297 -253
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/pai.js
CHANGED
|
@@ -4706,6 +4706,200 @@ var init_environment = __esm({
|
|
|
4706
4706
|
}
|
|
4707
4707
|
});
|
|
4708
4708
|
|
|
4709
|
+
// src/stages/validation/runner.ts
|
|
4710
|
+
import { join as join7 } from "path";
|
|
4711
|
+
import fs15 from "fs-extra";
|
|
4712
|
+
async function runTests(cwd) {
|
|
4713
|
+
const start = Date.now();
|
|
4714
|
+
const gstackPath = join7(cwd, ".pai", "gstack.json");
|
|
4715
|
+
let runner = "npm test";
|
|
4716
|
+
if (await fs15.pathExists(gstackPath)) {
|
|
4717
|
+
try {
|
|
4718
|
+
const config = await fs15.readJson(gstackPath);
|
|
4719
|
+
if (config.testRunner === "vitest") runner = "npx vitest run";
|
|
4720
|
+
else if (config.testRunner === "jest") runner = "npx jest";
|
|
4721
|
+
else if (config.testRunner === "mocha") runner = "npx mocha";
|
|
4722
|
+
} catch {
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
const pkgPath = join7(cwd, "package.json");
|
|
4726
|
+
if (await fs15.pathExists(pkgPath)) {
|
|
4727
|
+
try {
|
|
4728
|
+
const pkg5 = await fs15.readJson(pkgPath);
|
|
4729
|
+
if (!pkg5.scripts?.test || pkg5.scripts.test.includes("no test specified")) {
|
|
4730
|
+
return {
|
|
4731
|
+
runner,
|
|
4732
|
+
passed: false,
|
|
4733
|
+
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.",
|
|
4734
|
+
duration: Date.now() - start
|
|
4735
|
+
};
|
|
4736
|
+
}
|
|
4737
|
+
} catch {
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
try {
|
|
4741
|
+
const { execa } = await import("execa");
|
|
4742
|
+
const { stdout, stderr } = await execa("npm", ["test"], {
|
|
4743
|
+
cwd,
|
|
4744
|
+
timeout: 12e4,
|
|
4745
|
+
env: { ...process.env, CI: "true" }
|
|
4746
|
+
});
|
|
4747
|
+
return {
|
|
4748
|
+
runner,
|
|
4749
|
+
passed: true,
|
|
4750
|
+
output: stdout || stderr,
|
|
4751
|
+
duration: Date.now() - start
|
|
4752
|
+
};
|
|
4753
|
+
} catch (err) {
|
|
4754
|
+
const output = err instanceof Error ? err.message : String(err);
|
|
4755
|
+
return {
|
|
4756
|
+
runner,
|
|
4757
|
+
passed: false,
|
|
4758
|
+
output,
|
|
4759
|
+
duration: Date.now() - start
|
|
4760
|
+
};
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
var init_runner = __esm({
|
|
4764
|
+
"src/stages/validation/runner.ts"() {
|
|
4765
|
+
"use strict";
|
|
4766
|
+
}
|
|
4767
|
+
});
|
|
4768
|
+
|
|
4769
|
+
// src/stages/validation/harness.ts
|
|
4770
|
+
import { join as join8 } from "path";
|
|
4771
|
+
import fs16 from "fs-extra";
|
|
4772
|
+
async function runHarnessCheck(cwd) {
|
|
4773
|
+
const harnessPath = join8(cwd, ".pai", "harness.json");
|
|
4774
|
+
if (!await fs16.pathExists(harnessPath)) {
|
|
4775
|
+
return { enabled: false, specFile: null, rules: [], checks: [] };
|
|
4776
|
+
}
|
|
4777
|
+
let config;
|
|
4778
|
+
try {
|
|
4779
|
+
config = await fs16.readJson(harnessPath);
|
|
4780
|
+
} catch {
|
|
4781
|
+
return { enabled: false, specFile: null, rules: [], checks: [] };
|
|
4782
|
+
}
|
|
4783
|
+
const specFile = config.specFile ?? "docs/openspec.md";
|
|
4784
|
+
const rules = config.rules ?? [];
|
|
4785
|
+
const checks = [];
|
|
4786
|
+
if (rules.includes("spec-implementation-match")) {
|
|
4787
|
+
const specExists = await fs16.pathExists(join8(cwd, specFile));
|
|
4788
|
+
const srcExists = await fs16.pathExists(join8(cwd, "src"));
|
|
4789
|
+
checks.push({
|
|
4790
|
+
rule: "spec-implementation-match",
|
|
4791
|
+
passed: specExists && srcExists,
|
|
4792
|
+
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()
|
|
4793
|
+
});
|
|
4794
|
+
}
|
|
4795
|
+
if (rules.includes("api-contract-test")) {
|
|
4796
|
+
const testDir = await fs16.pathExists(join8(cwd, "tests"));
|
|
4797
|
+
const testDir2 = await fs16.pathExists(join8(cwd, "test"));
|
|
4798
|
+
checks.push({
|
|
4799
|
+
rule: "api-contract-test",
|
|
4800
|
+
passed: testDir || testDir2,
|
|
4801
|
+
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"
|
|
4802
|
+
});
|
|
4803
|
+
}
|
|
4804
|
+
return { enabled: true, specFile, rules, checks };
|
|
4805
|
+
}
|
|
4806
|
+
var init_harness = __esm({
|
|
4807
|
+
"src/stages/validation/harness.ts"() {
|
|
4808
|
+
"use strict";
|
|
4809
|
+
}
|
|
4810
|
+
});
|
|
4811
|
+
|
|
4812
|
+
// src/cli/commands/validate.cmd.ts
|
|
4813
|
+
var validate_cmd_exports = {};
|
|
4814
|
+
__export(validate_cmd_exports, {
|
|
4815
|
+
handleRobocoCliError: () => handleRobocoError,
|
|
4816
|
+
validateCommand: () => validateCommand
|
|
4817
|
+
});
|
|
4818
|
+
async function validateCommand(cwd, options = {}) {
|
|
4819
|
+
try {
|
|
4820
|
+
await withRoboco(
|
|
4821
|
+
cwd,
|
|
4822
|
+
"validation",
|
|
4823
|
+
"pai test",
|
|
4824
|
+
{ skipGates: options.skipGates, force: options.force, gate: STAGE_GATES.validation },
|
|
4825
|
+
async () => {
|
|
4826
|
+
section("\uD14C\uC2A4\uD2B8 \uC2E4\uD589");
|
|
4827
|
+
const testResult = await runTests(cwd);
|
|
4828
|
+
if (testResult.passed) {
|
|
4829
|
+
success(`\uD14C\uC2A4\uD2B8 \uD1B5\uACFC (${testResult.runner}, ${testResult.duration}ms)`);
|
|
4830
|
+
} else {
|
|
4831
|
+
error("\uD14C\uC2A4\uD2B8 \uC2E4\uD328");
|
|
4832
|
+
info(testResult.output.slice(0, 300));
|
|
4833
|
+
}
|
|
4834
|
+
section("\uD558\uB124\uC2A4 \uAC80\uC99D");
|
|
4835
|
+
const harness = await runHarnessCheck(cwd);
|
|
4836
|
+
if (!harness.enabled) {
|
|
4837
|
+
info("Harness \uC124\uC815 \uC5C6\uC74C \u2014 \uAC74\uB108\uB700");
|
|
4838
|
+
info("\uC124\uC815 \uCD94\uAC00: `pai add` \uC5D0\uC11C Harness Engineering \uC120\uD0DD");
|
|
4839
|
+
} else {
|
|
4840
|
+
for (const check of harness.checks) {
|
|
4841
|
+
if (check.passed) {
|
|
4842
|
+
success(`${check.rule}: ${check.detail}`);
|
|
4843
|
+
} else {
|
|
4844
|
+
warn(`${check.rule}: ${check.detail}`);
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
const allPassed = testResult.passed && harness.checks.every((c2) => c2.passed);
|
|
4849
|
+
console.log("");
|
|
4850
|
+
if (allPassed) {
|
|
4851
|
+
success("\uAC80\uC99D \uD1B5\uACFC!");
|
|
4852
|
+
} else if (!testResult.passed) {
|
|
4853
|
+
error("\uAC80\uC99D \uC2E4\uD328 \u2014 \uD14C\uC2A4\uD2B8\uB97C \uC218\uC815\uD558\uC138\uC694.");
|
|
4854
|
+
throw new Error("tests-failed");
|
|
4855
|
+
} else {
|
|
4856
|
+
warn("\uBD80\uBD84 \uD1B5\uACFC \u2014 \uD558\uB124\uC2A4 \uAC80\uC99D \uD56D\uBAA9\uC744 \uD655\uC778\uD558\uC138\uC694.");
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
);
|
|
4860
|
+
await syncHandoff(cwd).catch(() => {
|
|
4861
|
+
});
|
|
4862
|
+
} catch (err) {
|
|
4863
|
+
handleRobocoError(err);
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
function handleRobocoError(err) {
|
|
4867
|
+
if (err instanceof RobocoLockError) {
|
|
4868
|
+
error(err.message);
|
|
4869
|
+
process.exitCode = 1;
|
|
4870
|
+
return;
|
|
4871
|
+
}
|
|
4872
|
+
if (err instanceof GateFailedError) {
|
|
4873
|
+
error(`\uB2E8\uACC4 \uC9C4\uC785 \uC2E4\uD328 (${err.result.name})`);
|
|
4874
|
+
for (const v of err.result.violations) {
|
|
4875
|
+
warn(` \xB7 ${v.message}${v.location ? ` [${v.location}]` : ""}`);
|
|
4876
|
+
}
|
|
4877
|
+
hint("\uC6B0\uD68C \uC635\uC158: --skip-gates (\uACBD\uACE0\uB9CC) / --force (\uAC8C\uC774\uD2B8 \uBB34\uC2DC)");
|
|
4878
|
+
process.exitCode = 1;
|
|
4879
|
+
return;
|
|
4880
|
+
}
|
|
4881
|
+
if (err instanceof Error) {
|
|
4882
|
+
if (err.message === "tests-failed") {
|
|
4883
|
+
process.exitCode = 1;
|
|
4884
|
+
return;
|
|
4885
|
+
}
|
|
4886
|
+
error(err.message);
|
|
4887
|
+
process.exitCode = 1;
|
|
4888
|
+
return;
|
|
4889
|
+
}
|
|
4890
|
+
throw err;
|
|
4891
|
+
}
|
|
4892
|
+
var init_validate_cmd = __esm({
|
|
4893
|
+
"src/cli/commands/validate.cmd.ts"() {
|
|
4894
|
+
"use strict";
|
|
4895
|
+
init_ui();
|
|
4896
|
+
init_runner();
|
|
4897
|
+
init_harness();
|
|
4898
|
+
init_roboco();
|
|
4899
|
+
init_gates();
|
|
4900
|
+
}
|
|
4901
|
+
});
|
|
4902
|
+
|
|
4709
4903
|
// src/stages/evaluation/prompts/analyze.ts
|
|
4710
4904
|
var analyze_exports = {};
|
|
4711
4905
|
__export(analyze_exports, {
|
|
@@ -4762,8 +4956,8 @@ var analyzer_exports2 = {};
|
|
|
4762
4956
|
__export(analyzer_exports2, {
|
|
4763
4957
|
analyzeRepository: () => analyzeRepository
|
|
4764
4958
|
});
|
|
4765
|
-
import { join as
|
|
4766
|
-
import
|
|
4959
|
+
import { join as join9 } from "path";
|
|
4960
|
+
import fs17 from "fs-extra";
|
|
4767
4961
|
async function analyzeRepository(repoPath) {
|
|
4768
4962
|
try {
|
|
4769
4963
|
return await aiAnalysis(repoPath);
|
|
@@ -4824,14 +5018,14 @@ async function checkTestCoverage(repoPath) {
|
|
|
4824
5018
|
".nycrc"
|
|
4825
5019
|
];
|
|
4826
5020
|
for (const f of testConfigs) {
|
|
4827
|
-
const found = await
|
|
5021
|
+
const found = await fs17.pathExists(join9(repoPath, f));
|
|
4828
5022
|
findings.push({ item: f, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4829
5023
|
if (found) score += 20;
|
|
4830
5024
|
}
|
|
4831
5025
|
const testDirs = ["tests", "test", "__tests__", "spec"];
|
|
4832
5026
|
let hasTestDir = false;
|
|
4833
5027
|
for (const d of testDirs) {
|
|
4834
|
-
if (await
|
|
5028
|
+
if (await fs17.pathExists(join9(repoPath, d))) {
|
|
4835
5029
|
findings.push({ item: d, found: true, details: "\uD14C\uC2A4\uD2B8 \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC" });
|
|
4836
5030
|
hasTestDir = true;
|
|
4837
5031
|
score += 30;
|
|
@@ -4855,7 +5049,7 @@ async function checkCiCd(repoPath) {
|
|
|
4855
5049
|
{ path: ".circleci", label: "CircleCI" }
|
|
4856
5050
|
];
|
|
4857
5051
|
for (const { path: path10, label } of ciConfigs) {
|
|
4858
|
-
const found = await
|
|
5052
|
+
const found = await fs17.pathExists(join9(repoPath, path10));
|
|
4859
5053
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4860
5054
|
if (found) score += 40;
|
|
4861
5055
|
}
|
|
@@ -4874,7 +5068,7 @@ async function checkHooks(repoPath) {
|
|
|
4874
5068
|
{ path: ".claude/settings.json", label: "Claude Code settings" }
|
|
4875
5069
|
];
|
|
4876
5070
|
for (const { path: path10, label } of hookConfigs) {
|
|
4877
|
-
const found = await
|
|
5071
|
+
const found = await fs17.pathExists(join9(repoPath, path10));
|
|
4878
5072
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4879
5073
|
if (found) score += 20;
|
|
4880
5074
|
}
|
|
@@ -4892,7 +5086,7 @@ async function checkRepoStructure(repoPath) {
|
|
|
4892
5086
|
{ path: ".gitignore", label: ".gitignore" }
|
|
4893
5087
|
];
|
|
4894
5088
|
for (const { path: path10, label } of structureChecks) {
|
|
4895
|
-
const found = await
|
|
5089
|
+
const found = await fs17.pathExists(join9(repoPath, path10));
|
|
4896
5090
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4897
5091
|
if (found) score += 25;
|
|
4898
5092
|
}
|
|
@@ -4909,7 +5103,7 @@ async function checkDocumentation(repoPath) {
|
|
|
4909
5103
|
{ path: "docs/openspec.md", label: "OpenSpec PRD", points: 25 }
|
|
4910
5104
|
];
|
|
4911
5105
|
for (const { path: path10, label, points } of docChecks) {
|
|
4912
|
-
const found = await
|
|
5106
|
+
const found = await fs17.pathExists(join9(repoPath, path10));
|
|
4913
5107
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4914
5108
|
if (found) score += points;
|
|
4915
5109
|
}
|
|
@@ -4928,7 +5122,7 @@ async function checkHarnessEngineering(repoPath) {
|
|
|
4928
5122
|
{ path: ".pai/config.json", label: "PAI config", points: 10 }
|
|
4929
5123
|
];
|
|
4930
5124
|
for (const { path: path10, label, points } of harnessChecks) {
|
|
4931
|
-
const found = await
|
|
5125
|
+
const found = await fs17.pathExists(join9(repoPath, path10));
|
|
4932
5126
|
findings.push({ item: label, found, details: found ? "\uC874\uC7AC" : "\uC5C6\uC74C" });
|
|
4933
5127
|
if (found) score += points;
|
|
4934
5128
|
}
|
|
@@ -5311,56 +5505,56 @@ __export(shell_cd_exports, {
|
|
|
5311
5505
|
installShellHelper: () => installShellHelper,
|
|
5312
5506
|
requestCdAfter: () => requestCdAfter
|
|
5313
5507
|
});
|
|
5314
|
-
import { join as
|
|
5508
|
+
import { join as join10 } from "path";
|
|
5315
5509
|
import { homedir as homedir2 } from "os";
|
|
5316
|
-
import
|
|
5510
|
+
import fs18 from "fs-extra";
|
|
5317
5511
|
async function requestCdAfter(targetDir) {
|
|
5318
|
-
await
|
|
5319
|
-
await
|
|
5512
|
+
await fs18.ensureDir(PAI_DIR);
|
|
5513
|
+
await fs18.writeFile(CD_FILE, targetDir);
|
|
5320
5514
|
}
|
|
5321
5515
|
async function installShellHelper() {
|
|
5322
|
-
await
|
|
5516
|
+
await fs18.ensureDir(PAI_DIR);
|
|
5323
5517
|
if (isWindows) {
|
|
5324
5518
|
return installPowerShellHelper();
|
|
5325
5519
|
}
|
|
5326
5520
|
return installBashHelper();
|
|
5327
5521
|
}
|
|
5328
5522
|
async function installBashHelper() {
|
|
5329
|
-
await
|
|
5523
|
+
await fs18.writeFile(HELPER_FILE_SH, BASH_HELPER);
|
|
5330
5524
|
const rcFile = getShellRcPath();
|
|
5331
5525
|
const sourceLine = 'source "$HOME/.pai/shell-helper.sh"';
|
|
5332
|
-
if (await
|
|
5333
|
-
const content = await
|
|
5526
|
+
if (await fs18.pathExists(rcFile)) {
|
|
5527
|
+
const content = await fs18.readFile(rcFile, "utf8");
|
|
5334
5528
|
if (content.includes("shell-helper.sh")) {
|
|
5335
5529
|
return true;
|
|
5336
5530
|
}
|
|
5337
|
-
await
|
|
5531
|
+
await fs18.appendFile(rcFile, `
|
|
5338
5532
|
# PAI \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9
|
|
5339
5533
|
${sourceLine}
|
|
5340
5534
|
`);
|
|
5341
5535
|
return false;
|
|
5342
5536
|
}
|
|
5343
|
-
await
|
|
5537
|
+
await fs18.writeFile(rcFile, `${sourceLine}
|
|
5344
5538
|
`);
|
|
5345
5539
|
return false;
|
|
5346
5540
|
}
|
|
5347
5541
|
async function installPowerShellHelper() {
|
|
5348
|
-
await
|
|
5542
|
+
await fs18.writeFile(HELPER_FILE_PS1, POWERSHELL_HELPER);
|
|
5349
5543
|
const rcFile = getShellRcPath();
|
|
5350
5544
|
const sourceLine = '. "$env:USERPROFILE\\.pai\\shell-helper.ps1"';
|
|
5351
|
-
await
|
|
5352
|
-
if (await
|
|
5353
|
-
const content = await
|
|
5545
|
+
await fs18.ensureDir(join10(rcFile, ".."));
|
|
5546
|
+
if (await fs18.pathExists(rcFile)) {
|
|
5547
|
+
const content = await fs18.readFile(rcFile, "utf8");
|
|
5354
5548
|
if (content.includes("shell-helper.ps1")) {
|
|
5355
5549
|
return true;
|
|
5356
5550
|
}
|
|
5357
|
-
await
|
|
5551
|
+
await fs18.appendFile(rcFile, `
|
|
5358
5552
|
# PAI \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9
|
|
5359
5553
|
${sourceLine}
|
|
5360
5554
|
`);
|
|
5361
5555
|
return false;
|
|
5362
5556
|
}
|
|
5363
|
-
await
|
|
5557
|
+
await fs18.writeFile(rcFile, `${sourceLine}
|
|
5364
5558
|
`);
|
|
5365
5559
|
return false;
|
|
5366
5560
|
}
|
|
@@ -5369,10 +5563,10 @@ var init_shell_cd = __esm({
|
|
|
5369
5563
|
"src/utils/shell-cd.ts"() {
|
|
5370
5564
|
"use strict";
|
|
5371
5565
|
init_platform();
|
|
5372
|
-
PAI_DIR =
|
|
5373
|
-
CD_FILE =
|
|
5374
|
-
HELPER_FILE_SH =
|
|
5375
|
-
HELPER_FILE_PS1 =
|
|
5566
|
+
PAI_DIR = join10(homedir2(), ".pai");
|
|
5567
|
+
CD_FILE = join10(PAI_DIR, ".cd-after");
|
|
5568
|
+
HELPER_FILE_SH = join10(PAI_DIR, "shell-helper.sh");
|
|
5569
|
+
HELPER_FILE_PS1 = join10(PAI_DIR, "shell-helper.ps1");
|
|
5376
5570
|
BASH_HELPER = `# PAI shell helper \u2014 \uC790\uB3D9 \uB514\uB809\uD1A0\uB9AC \uC774\uB3D9 \uC9C0\uC6D0
|
|
5377
5571
|
pai() {
|
|
5378
5572
|
local cd_target="$HOME/.pai/.cd-after"
|
|
@@ -5424,7 +5618,7 @@ __export(claude_settings_exports, {
|
|
|
5424
5618
|
});
|
|
5425
5619
|
import os3 from "os";
|
|
5426
5620
|
import path9 from "path";
|
|
5427
|
-
import
|
|
5621
|
+
import fs19 from "fs-extra";
|
|
5428
5622
|
function getClaudeSettingsPath(homeDir = os3.homedir()) {
|
|
5429
5623
|
return path9.join(homeDir, ".claude", "settings.json");
|
|
5430
5624
|
}
|
|
@@ -5441,13 +5635,13 @@ async function enableOmcPlugin(options = {}) {
|
|
|
5441
5635
|
const pluginId = options.pluginId ?? DEFAULT_PLUGIN_ID;
|
|
5442
5636
|
const wantBackup = options.backup ?? true;
|
|
5443
5637
|
const settingsPath = getClaudeSettingsPath();
|
|
5444
|
-
await
|
|
5445
|
-
if (!await
|
|
5638
|
+
await fs19.ensureDir(path9.dirname(settingsPath));
|
|
5639
|
+
if (!await fs19.pathExists(settingsPath)) {
|
|
5446
5640
|
const skeleton = buildSkeleton(marketplaceId, marketplaceUrl, pluginId);
|
|
5447
|
-
await
|
|
5641
|
+
await fs19.writeFile(settingsPath, JSON.stringify(skeleton, null, 2) + "\n", "utf8");
|
|
5448
5642
|
return { action: "created", settingsPath };
|
|
5449
5643
|
}
|
|
5450
|
-
const raw = await
|
|
5644
|
+
const raw = await fs19.readFile(settingsPath, "utf8");
|
|
5451
5645
|
let parsed;
|
|
5452
5646
|
try {
|
|
5453
5647
|
const value = parseJsonWithBom(raw);
|
|
@@ -5457,7 +5651,7 @@ async function enableOmcPlugin(options = {}) {
|
|
|
5457
5651
|
parsed = value;
|
|
5458
5652
|
} catch (err) {
|
|
5459
5653
|
const backupPath2 = `${settingsPath}.backup-${timestampSuffix()}`;
|
|
5460
|
-
await
|
|
5654
|
+
await fs19.copy(settingsPath, backupPath2);
|
|
5461
5655
|
throw new ClaudeSettingsError(
|
|
5462
5656
|
`settings.json \uD30C\uC2F1 \uC2E4\uD328: ${err.message}. \uBC31\uC5C5: ${backupPath2}`,
|
|
5463
5657
|
backupPath2
|
|
@@ -5469,10 +5663,10 @@ async function enableOmcPlugin(options = {}) {
|
|
|
5469
5663
|
let backupPath;
|
|
5470
5664
|
if (wantBackup) {
|
|
5471
5665
|
backupPath = `${settingsPath}.backup-${timestampSuffix()}`;
|
|
5472
|
-
await
|
|
5666
|
+
await fs19.copy(settingsPath, backupPath);
|
|
5473
5667
|
}
|
|
5474
5668
|
const merged = mergeOmcIntoSettings(parsed, marketplaceId, marketplaceUrl, pluginId);
|
|
5475
|
-
await
|
|
5669
|
+
await fs19.writeFile(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
5476
5670
|
return { action: "added", settingsPath, backupPath };
|
|
5477
5671
|
}
|
|
5478
5672
|
function buildSkeleton(marketplaceId, marketplaceUrl, pluginId) {
|
|
@@ -5527,12 +5721,12 @@ var init_claude_settings = __esm({
|
|
|
5527
5721
|
|
|
5528
5722
|
// src/stages/evaluation/cache.ts
|
|
5529
5723
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
5530
|
-
import { join as
|
|
5724
|
+
import { join as join11 } from "path";
|
|
5531
5725
|
import { createHash } from "crypto";
|
|
5532
5726
|
function computeRepoHash(repoPath) {
|
|
5533
5727
|
const hash = createHash("sha256");
|
|
5534
5728
|
for (const file of FILES_TO_HASH) {
|
|
5535
|
-
const fullPath =
|
|
5729
|
+
const fullPath = join11(repoPath, file);
|
|
5536
5730
|
try {
|
|
5537
5731
|
const content = readFileSync(fullPath);
|
|
5538
5732
|
hash.update(`${file}:${content.length}`);
|
|
@@ -5543,7 +5737,7 @@ function computeRepoHash(repoPath) {
|
|
|
5543
5737
|
return hash.digest("hex").slice(0, 16);
|
|
5544
5738
|
}
|
|
5545
5739
|
function getCachePath(repoPath) {
|
|
5546
|
-
return
|
|
5740
|
+
return join11(repoPath, CACHE_DIR, CACHE_FILE);
|
|
5547
5741
|
}
|
|
5548
5742
|
function loadCache(repoPath) {
|
|
5549
5743
|
try {
|
|
@@ -5554,7 +5748,7 @@ function loadCache(repoPath) {
|
|
|
5554
5748
|
}
|
|
5555
5749
|
}
|
|
5556
5750
|
function saveCache(repoPath, store) {
|
|
5557
|
-
const cacheDir =
|
|
5751
|
+
const cacheDir = join11(repoPath, CACHE_DIR, "cache");
|
|
5558
5752
|
if (!existsSync(cacheDir)) {
|
|
5559
5753
|
mkdirSync(cacheDir, { recursive: true });
|
|
5560
5754
|
}
|
|
@@ -5608,200 +5802,6 @@ var init_cache = __esm({
|
|
|
5608
5802
|
}
|
|
5609
5803
|
});
|
|
5610
5804
|
|
|
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 {
|
|
5625
|
-
}
|
|
5626
|
-
}
|
|
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
|
-
}
|
|
5641
|
-
}
|
|
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
|
-
};
|
|
5663
|
-
}
|
|
5664
|
-
}
|
|
5665
|
-
var init_runner = __esm({
|
|
5666
|
-
"src/stages/validation/runner.ts"() {
|
|
5667
|
-
"use strict";
|
|
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
5805
|
// src/cli/commands/evaluate.cmd.ts
|
|
5806
5806
|
var evaluate_cmd_exports = {};
|
|
5807
5807
|
__export(evaluate_cmd_exports, {
|
|
@@ -6234,7 +6234,24 @@ async function setupInDirectory(projectDir, projectName) {
|
|
|
6234
6234
|
config,
|
|
6235
6235
|
previousResults: /* @__PURE__ */ new Map()
|
|
6236
6236
|
};
|
|
6237
|
-
|
|
6237
|
+
let stageResult;
|
|
6238
|
+
try {
|
|
6239
|
+
await withRoboco(
|
|
6240
|
+
projectDir,
|
|
6241
|
+
"environment",
|
|
6242
|
+
"pai init",
|
|
6243
|
+
{},
|
|
6244
|
+
async () => {
|
|
6245
|
+
stageResult = await environmentStage.run(input);
|
|
6246
|
+
}
|
|
6247
|
+
);
|
|
6248
|
+
await syncHandoff(projectDir).catch(() => {
|
|
6249
|
+
});
|
|
6250
|
+
} catch (err) {
|
|
6251
|
+
handleRobocoError(err);
|
|
6252
|
+
return;
|
|
6253
|
+
}
|
|
6254
|
+
const result = stageResult;
|
|
6238
6255
|
if (result.status === "success") {
|
|
6239
6256
|
try {
|
|
6240
6257
|
const { requestCdAfter: requestCdAfter2 } = await Promise.resolve().then(() => (init_shell_cd(), shell_cd_exports));
|
|
@@ -6657,6 +6674,8 @@ var init_init_cmd = __esm({
|
|
|
6657
6674
|
init_environment();
|
|
6658
6675
|
init_config();
|
|
6659
6676
|
init_detector();
|
|
6677
|
+
init_roboco();
|
|
6678
|
+
init_validate_cmd();
|
|
6660
6679
|
init_ui();
|
|
6661
6680
|
init_logger();
|
|
6662
6681
|
}
|
|
@@ -7260,6 +7279,29 @@ async function runPipeline(cwd, config, options = {}) {
|
|
|
7260
7279
|
config,
|
|
7261
7280
|
previousResults: ctx.getResults()
|
|
7262
7281
|
};
|
|
7282
|
+
if (!options.force) {
|
|
7283
|
+
const gateResult = await STAGE_GATES[stageName](cwd).catch(() => null);
|
|
7284
|
+
if (gateResult) {
|
|
7285
|
+
await saveGateResult(cwd, `${stageName}.entry`, gateResult).catch(() => {
|
|
7286
|
+
});
|
|
7287
|
+
if (!gateResult.passed && !options.skipGates) {
|
|
7288
|
+
error(`${stageName} \uAC8C\uC774\uD2B8 \uC2E4\uD328 \u2014 \uD30C\uC774\uD504\uB77C\uC778 \uC911\uB2E8`);
|
|
7289
|
+
for (const v of gateResult.violations) {
|
|
7290
|
+
warn(` \xB7 ${v.message}${v.location ? ` [${v.location}]` : ""}`);
|
|
7291
|
+
}
|
|
7292
|
+
hint("\uC6B0\uD68C: pai run --skip-gates (\uACBD\uACE0) / --force (\uBB34\uC2DC)");
|
|
7293
|
+
await markStageEnd(cwd, stageName, false, `\uAC8C\uC774\uD2B8 \uC2E4\uD328: ${gateResult.name}`).catch(() => {
|
|
7294
|
+
});
|
|
7295
|
+
shouldStop = true;
|
|
7296
|
+
break;
|
|
7297
|
+
}
|
|
7298
|
+
if (!gateResult.passed && options.skipGates) {
|
|
7299
|
+
for (const v of gateResult.violations) {
|
|
7300
|
+
warn(` [\uAC8C\uC774\uD2B8 \uACBD\uACE0] ${v.message}`);
|
|
7301
|
+
}
|
|
7302
|
+
}
|
|
7303
|
+
}
|
|
7304
|
+
}
|
|
7263
7305
|
if (stage.canSkip(input)) {
|
|
7264
7306
|
info(`${stageName} \u2014 \uAC74\uB108\uB700 (\uC774\uBBF8 \uC644\uB8CC)`);
|
|
7265
7307
|
ctx.setResult(stageName, {
|
|
@@ -7272,8 +7314,14 @@ async function runPipeline(cwd, config, options = {}) {
|
|
|
7272
7314
|
});
|
|
7273
7315
|
continue;
|
|
7274
7316
|
}
|
|
7317
|
+
await markStageStart(cwd, stageName, "pai run").catch(() => {
|
|
7318
|
+
});
|
|
7275
7319
|
const result = await stage.run(input);
|
|
7276
7320
|
ctx.setResult(stageName, result);
|
|
7321
|
+
const stageSuccess = result.status !== "failed";
|
|
7322
|
+
const failReason = result.errors[0]?.message;
|
|
7323
|
+
await markStageEnd(cwd, stageName, stageSuccess, failReason).catch(() => {
|
|
7324
|
+
});
|
|
7277
7325
|
switch (result.status) {
|
|
7278
7326
|
case "success":
|
|
7279
7327
|
success(`${stageName} \uC644\uB8CC (${result.duration}ms)`);
|
|
@@ -7321,6 +7369,8 @@ var init_pipeline = __esm({
|
|
|
7321
7369
|
init_execution();
|
|
7322
7370
|
init_validation();
|
|
7323
7371
|
init_evaluation();
|
|
7372
|
+
init_roboco();
|
|
7373
|
+
init_gates();
|
|
7324
7374
|
init_ui();
|
|
7325
7375
|
STAGES = {
|
|
7326
7376
|
environment: environmentStage,
|
|
@@ -7340,23 +7390,17 @@ __export(pipeline_cmd_exports, {
|
|
|
7340
7390
|
async function pipelineCommand(cwd, options) {
|
|
7341
7391
|
printBanner();
|
|
7342
7392
|
try {
|
|
7343
|
-
await
|
|
7344
|
-
|
|
7345
|
-
"
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
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
|
-
);
|
|
7393
|
+
await acquireLock(cwd, "environment", "pai run", PIPELINE_TTL_MS);
|
|
7394
|
+
try {
|
|
7395
|
+
const config = await loadConfig(cwd) ?? createDefaultConfig("my-project", "prototype");
|
|
7396
|
+
const pipelineOpts = { skipGates: options.skipGates, force: options.force };
|
|
7397
|
+
if (options.from) pipelineOpts.from = options.from;
|
|
7398
|
+
if (options.only) pipelineOpts.only = options.only.split(",").map((s) => s.trim());
|
|
7399
|
+
await runPipeline(cwd, config, pipelineOpts);
|
|
7400
|
+
} finally {
|
|
7401
|
+
await releaseLock(cwd).catch(() => {
|
|
7402
|
+
});
|
|
7403
|
+
}
|
|
7360
7404
|
await syncHandoff(cwd).catch(() => {
|
|
7361
7405
|
});
|
|
7362
7406
|
} catch (err) {
|