spets 0.1.4 → 0.1.6
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/README.md +4 -2
- package/dist/index.js +155 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,13 +100,15 @@ npx spets start "task" --pr 42
|
|
|
100
100
|
Issue/PR 코멘트로 워크플로우를 제어합니다:
|
|
101
101
|
|
|
102
102
|
- `/approve` - 현재 스텝 승인하고 다음 스텝 진행
|
|
103
|
+
- `/approve --pr` - 승인하고 Pull Request 생성
|
|
104
|
+
- `/approve --issue` - 승인하고 Issue 생성/업데이트
|
|
103
105
|
- `/revise <feedback>` - 피드백과 함께 현재 스텝 재생성
|
|
104
106
|
- `/reject` - 워크플로우 중단
|
|
105
107
|
|
|
106
108
|
### 필요 설정
|
|
107
109
|
|
|
108
110
|
Repository Secrets에 추가:
|
|
109
|
-
- `
|
|
111
|
+
- `CLAUDE_CODE_OAUTH_TOKEN` - Claude 인증 토큰 (`claude setup-token`으로 생성)
|
|
110
112
|
|
|
111
113
|
## Claude Code Plugin
|
|
112
114
|
|
|
@@ -143,7 +145,7 @@ hooks:
|
|
|
143
145
|
## Requirements
|
|
144
146
|
|
|
145
147
|
- Node.js >= 18
|
|
146
|
-
- Claude CLI (`claude` command) or
|
|
148
|
+
- Claude CLI (`claude` command) or CLAUDE_CODE_OAUTH_TOKEN
|
|
147
149
|
- GitHub CLI (`gh`) - GitHub 연동 사용 시
|
|
148
150
|
|
|
149
151
|
## License
|
package/dist/index.js
CHANGED
|
@@ -76,7 +76,7 @@ async function initCommand(options) {
|
|
|
76
76
|
console.log(" 1. Edit .spets/config.yml to customize your workflow");
|
|
77
77
|
console.log(" 2. Customize step instructions in .spets/steps/");
|
|
78
78
|
if (options.github) {
|
|
79
|
-
console.log(" 3. Add
|
|
79
|
+
console.log(" 3. Add CLAUDE_CODE_OAUTH_TOKEN to your repo secrets");
|
|
80
80
|
console.log(' 4. Create a new Issue using the "Spets Task" template');
|
|
81
81
|
} else {
|
|
82
82
|
console.log(' 3. Run: spets start "your task description"');
|
|
@@ -443,6 +443,11 @@ on:
|
|
|
443
443
|
issue_comment:
|
|
444
444
|
types: [created]
|
|
445
445
|
|
|
446
|
+
permissions:
|
|
447
|
+
contents: write
|
|
448
|
+
issues: write
|
|
449
|
+
pull-requests: write
|
|
450
|
+
|
|
446
451
|
jobs:
|
|
447
452
|
# Start workflow when a spets Issue is created
|
|
448
453
|
start-workflow:
|
|
@@ -495,7 +500,7 @@ jobs:
|
|
|
495
500
|
npx spets start "$TASK" --github --issue ${gh("github.event.issue.number")}
|
|
496
501
|
env:
|
|
497
502
|
TASK: ${gh("steps.parse.outputs.task")}
|
|
498
|
-
|
|
503
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${gh("secrets.CLAUDE_CODE_OAUTH_TOKEN")}
|
|
499
504
|
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
500
505
|
|
|
501
506
|
- name: Push changes
|
|
@@ -553,7 +558,7 @@ jobs:
|
|
|
553
558
|
npx spets github --issue ${gh("github.event.issue.number")} --comment "$COMMENT"
|
|
554
559
|
env:
|
|
555
560
|
COMMENT: ${gh("github.event.comment.body")}
|
|
556
|
-
|
|
561
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${gh("secrets.CLAUDE_CODE_OAUTH_TOKEN")}
|
|
557
562
|
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
558
563
|
|
|
559
564
|
- name: Push changes
|
|
@@ -697,7 +702,7 @@ var Executor = class {
|
|
|
697
702
|
this.config = options.config;
|
|
698
703
|
this.cwd = options.cwd || process.cwd();
|
|
699
704
|
}
|
|
700
|
-
async executeWorkflow(taskId, userQuery, startIndex = 0) {
|
|
705
|
+
async executeWorkflow(taskId, userQuery, startIndex = 0, feedback) {
|
|
701
706
|
let previousOutput;
|
|
702
707
|
if (startIndex > 0) {
|
|
703
708
|
const prevStepName = this.config.steps[startIndex - 1];
|
|
@@ -717,7 +722,9 @@ var Executor = class {
|
|
|
717
722
|
instruction: stepDef.instruction,
|
|
718
723
|
template: stepDef.template,
|
|
719
724
|
previousOutput,
|
|
720
|
-
outputPath: getOutputPath(taskId, stepName, this.cwd)
|
|
725
|
+
outputPath: getOutputPath(taskId, stepName, this.cwd),
|
|
726
|
+
feedback: i === startIndex ? feedback : void 0
|
|
727
|
+
// Only apply feedback to the target step
|
|
721
728
|
};
|
|
722
729
|
if (this.config.hooks?.preStep) {
|
|
723
730
|
await runHook(this.config.hooks.preStep, {
|
|
@@ -1030,6 +1037,12 @@ function parseGitHubCommand(comment) {
|
|
|
1030
1037
|
if (trimmed === "/approve") {
|
|
1031
1038
|
return { command: "approve" };
|
|
1032
1039
|
}
|
|
1040
|
+
if (trimmed === "/approve --pr") {
|
|
1041
|
+
return { command: "approve", createPR: true };
|
|
1042
|
+
}
|
|
1043
|
+
if (trimmed === "/approve --issue") {
|
|
1044
|
+
return { command: "approve", createIssue: true };
|
|
1045
|
+
}
|
|
1033
1046
|
if (trimmed === "/reject") {
|
|
1034
1047
|
return { command: "reject" };
|
|
1035
1048
|
}
|
|
@@ -1055,6 +1068,8 @@ function parseGitHubCommand(comment) {
|
|
|
1055
1068
|
var GitHubPlatform = class extends BasePlatform {
|
|
1056
1069
|
config;
|
|
1057
1070
|
currentTaskId;
|
|
1071
|
+
currentOutputPath;
|
|
1072
|
+
lastQuestions;
|
|
1058
1073
|
constructor(config) {
|
|
1059
1074
|
super();
|
|
1060
1075
|
this.config = config;
|
|
@@ -1062,11 +1077,16 @@ var GitHubPlatform = class extends BasePlatform {
|
|
|
1062
1077
|
setTaskId(taskId) {
|
|
1063
1078
|
this.currentTaskId = taskId;
|
|
1064
1079
|
}
|
|
1080
|
+
setBranch(branch) {
|
|
1081
|
+
this.config.branch = branch;
|
|
1082
|
+
}
|
|
1065
1083
|
async generateDocument(context) {
|
|
1066
1084
|
this.currentTaskId = context.taskId;
|
|
1085
|
+
this.currentOutputPath = context.outputPath;
|
|
1067
1086
|
const prompt = this.buildPrompt(context);
|
|
1068
1087
|
const response = await this.callClaude(prompt);
|
|
1069
1088
|
const { document, questions } = this.parseResponse(response);
|
|
1089
|
+
this.lastQuestions = questions;
|
|
1070
1090
|
return { document, questions };
|
|
1071
1091
|
}
|
|
1072
1092
|
async askUser(questions) {
|
|
@@ -1077,7 +1097,7 @@ var GitHubPlatform = class extends BasePlatform {
|
|
|
1077
1097
|
throw new PauseForInputError("questions", questions);
|
|
1078
1098
|
}
|
|
1079
1099
|
async requestApproval(doc, stepName) {
|
|
1080
|
-
const comment = this.formatApprovalComment(doc, stepName, this.currentTaskId);
|
|
1100
|
+
const comment = this.formatApprovalComment(doc, stepName, this.currentTaskId, this.currentOutputPath, this.lastQuestions);
|
|
1081
1101
|
await this.postComment(comment);
|
|
1082
1102
|
console.log("\n\u23F8\uFE0F Waiting for approval on GitHub...");
|
|
1083
1103
|
console.log(" Comment /approve, /revise <feedback>, or /reject on the PR/Issue.");
|
|
@@ -1110,39 +1130,77 @@ var GitHubPlatform = class extends BasePlatform {
|
|
|
1110
1130
|
lines.push("");
|
|
1111
1131
|
}
|
|
1112
1132
|
lines.push("---");
|
|
1113
|
-
lines.push("
|
|
1133
|
+
lines.push("");
|
|
1134
|
+
lines.push("**How to answer:**");
|
|
1135
|
+
lines.push("");
|
|
1114
1136
|
lines.push("```");
|
|
1115
1137
|
lines.push("/answer");
|
|
1116
1138
|
for (let i = 0; i < questions.length; i++) {
|
|
1117
|
-
lines.push(`Q${i + 1}: your answer
|
|
1139
|
+
lines.push(`Q${i + 1}: <your answer for question ${i + 1}>`);
|
|
1118
1140
|
}
|
|
1119
1141
|
lines.push("```");
|
|
1142
|
+
lines.push("");
|
|
1143
|
+
lines.push("Example:");
|
|
1144
|
+
lines.push("```");
|
|
1145
|
+
lines.push("/answer");
|
|
1146
|
+
lines.push("Q1: Use TypeScript");
|
|
1147
|
+
lines.push("Q2: Yes, add unit tests");
|
|
1148
|
+
lines.push("```");
|
|
1120
1149
|
return lines.join("\n");
|
|
1121
1150
|
}
|
|
1122
|
-
formatApprovalComment(doc, stepName, taskId) {
|
|
1151
|
+
formatApprovalComment(doc, stepName, taskId, outputPath, openQuestions) {
|
|
1152
|
+
const relativePath = outputPath ? outputPath.replace(process.cwd() + "/", "") : `.spets/outputs/${taskId}/${stepName}.md`;
|
|
1153
|
+
const { owner, repo, branch } = this.config;
|
|
1154
|
+
const fileLink = branch ? `[\`${relativePath}\`](https://github.com/${owner}/${repo}/blob/${branch}/${relativePath})` : `\`${relativePath}\``;
|
|
1155
|
+
const escapedDoc = doc.replace(/```/g, "\\`\\`\\`");
|
|
1123
1156
|
const lines = [
|
|
1124
1157
|
`## \u{1F4C4} Spets: ${stepName} - Review Required`,
|
|
1125
1158
|
"",
|
|
1126
1159
|
`> Task ID: \`${taskId}\``,
|
|
1160
|
+
`> Output: ${fileLink}`,
|
|
1127
1161
|
"",
|
|
1128
1162
|
"<details>",
|
|
1129
1163
|
"<summary>\u{1F4DD} View Document</summary>",
|
|
1130
1164
|
"",
|
|
1131
|
-
|
|
1132
|
-
doc,
|
|
1133
|
-
"```",
|
|
1134
|
-
"",
|
|
1135
|
-
"</details>",
|
|
1136
|
-
"",
|
|
1137
|
-
"---",
|
|
1165
|
+
escapedDoc,
|
|
1138
1166
|
"",
|
|
1139
|
-
"
|
|
1140
|
-
"| Command | Description |",
|
|
1141
|
-
"|---------|-------------|",
|
|
1142
|
-
"| `/approve` | Approve and continue to next step |",
|
|
1143
|
-
"| `/revise <feedback>` | Request changes with feedback |",
|
|
1144
|
-
"| `/reject` | Reject and stop workflow |"
|
|
1167
|
+
"</details>"
|
|
1145
1168
|
];
|
|
1169
|
+
if (openQuestions && openQuestions.length > 0) {
|
|
1170
|
+
lines.push("");
|
|
1171
|
+
lines.push("---");
|
|
1172
|
+
lines.push("");
|
|
1173
|
+
lines.push("### \u2753 Open Questions");
|
|
1174
|
+
lines.push("");
|
|
1175
|
+
for (let i = 0; i < openQuestions.length; i++) {
|
|
1176
|
+
const q = openQuestions[i];
|
|
1177
|
+
lines.push(`**Q${i + 1}:** ${q.question}`);
|
|
1178
|
+
if (q.context) {
|
|
1179
|
+
lines.push(`> ${q.context}`);
|
|
1180
|
+
}
|
|
1181
|
+
lines.push("");
|
|
1182
|
+
}
|
|
1183
|
+
lines.push("**To answer questions:**");
|
|
1184
|
+
lines.push("```");
|
|
1185
|
+
lines.push("/answer");
|
|
1186
|
+
for (let i = 0; i < openQuestions.length; i++) {
|
|
1187
|
+
lines.push(`Q${i + 1}: <your answer>`);
|
|
1188
|
+
}
|
|
1189
|
+
lines.push("```");
|
|
1190
|
+
}
|
|
1191
|
+
lines.push("");
|
|
1192
|
+
lines.push("---");
|
|
1193
|
+
lines.push("");
|
|
1194
|
+
lines.push("**Commands:**");
|
|
1195
|
+
lines.push("| Command | Description |");
|
|
1196
|
+
lines.push("|---------|-------------|");
|
|
1197
|
+
lines.push("| `/approve` | Approve and continue to next step |");
|
|
1198
|
+
lines.push("| `/approve --pr` | Approve and create a Pull Request |");
|
|
1199
|
+
lines.push("| `/approve --issue` | Approve and create/update an Issue |");
|
|
1200
|
+
lines.push("| `/revise <feedback>` | Request changes with feedback |");
|
|
1201
|
+
lines.push("| `/reject` | Reject and stop workflow |");
|
|
1202
|
+
lines.push("");
|
|
1203
|
+
lines.push("Example: `/revise Please add more details about error handling`");
|
|
1146
1204
|
return lines.join("\n");
|
|
1147
1205
|
}
|
|
1148
1206
|
async postPRComment(body) {
|
|
@@ -1206,12 +1264,11 @@ var GitHubPlatform = class extends BasePlatform {
|
|
|
1206
1264
|
async callClaude(prompt) {
|
|
1207
1265
|
return new Promise((resolve, reject) => {
|
|
1208
1266
|
const proc = spawn3("claude", [
|
|
1209
|
-
"
|
|
1267
|
+
"-p",
|
|
1268
|
+
prompt,
|
|
1210
1269
|
"--permission-mode",
|
|
1211
1270
|
"bypassPermissions"
|
|
1212
|
-
], { stdio: ["
|
|
1213
|
-
proc.stdin.write(prompt);
|
|
1214
|
-
proc.stdin.end();
|
|
1271
|
+
], { stdio: ["ignore", "pipe", "pipe"] });
|
|
1215
1272
|
let stdout = "";
|
|
1216
1273
|
let stderr = "";
|
|
1217
1274
|
proc.stdout.on("data", (data) => {
|
|
@@ -1222,7 +1279,7 @@ var GitHubPlatform = class extends BasePlatform {
|
|
|
1222
1279
|
});
|
|
1223
1280
|
proc.on("close", (code) => {
|
|
1224
1281
|
if (code !== 0) {
|
|
1225
|
-
reject(new Error(`Claude CLI failed
|
|
1282
|
+
reject(new Error(`Claude CLI failed (code ${code}): stderr=${stderr}, stdout=${stdout}`));
|
|
1226
1283
|
} else {
|
|
1227
1284
|
resolve(stdout);
|
|
1228
1285
|
}
|
|
@@ -1317,6 +1374,13 @@ _Managed by [Spets](https://github.com/eatnug/spets)_`;
|
|
|
1317
1374
|
}
|
|
1318
1375
|
return parseInt(match[1], 10);
|
|
1319
1376
|
}
|
|
1377
|
+
function getCurrentBranch() {
|
|
1378
|
+
try {
|
|
1379
|
+
return execSync3("git branch --show-current", { encoding: "utf-8" }).trim() || void 0;
|
|
1380
|
+
} catch {
|
|
1381
|
+
return void 0;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1320
1384
|
function findLinkedIssueOrPR(info) {
|
|
1321
1385
|
try {
|
|
1322
1386
|
const result = execSync3(
|
|
@@ -1386,7 +1450,8 @@ async function startCommand(query, options) {
|
|
|
1386
1450
|
owner: githubInfo.owner,
|
|
1387
1451
|
repo: githubInfo.repo,
|
|
1388
1452
|
prNumber,
|
|
1389
|
-
issueNumber
|
|
1453
|
+
issueNumber,
|
|
1454
|
+
branch: getCurrentBranch()
|
|
1390
1455
|
});
|
|
1391
1456
|
console.log(`Starting workflow: ${taskId}`);
|
|
1392
1457
|
console.log(`Query: ${query}`);
|
|
@@ -1778,6 +1843,13 @@ function getGitHubInfo2(cwd) {
|
|
|
1778
1843
|
}
|
|
1779
1844
|
return null;
|
|
1780
1845
|
}
|
|
1846
|
+
function getCurrentBranch2(cwd) {
|
|
1847
|
+
try {
|
|
1848
|
+
return execSync4("git branch --show-current", { encoding: "utf-8", cwd }).trim();
|
|
1849
|
+
} catch {
|
|
1850
|
+
return void 0;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1781
1853
|
async function githubCommand(options) {
|
|
1782
1854
|
const cwd = process.cwd();
|
|
1783
1855
|
if (!spetsExists(cwd)) {
|
|
@@ -1837,12 +1909,20 @@ async function githubCommand(options) {
|
|
|
1837
1909
|
owner,
|
|
1838
1910
|
repo,
|
|
1839
1911
|
prNumber: pr ? parseInt(pr, 10) : void 0,
|
|
1840
|
-
issueNumber: issue ? parseInt(issue, 10) : void 0
|
|
1912
|
+
issueNumber: issue ? parseInt(issue, 10) : void 0,
|
|
1913
|
+
branch: getCurrentBranch2(cwd)
|
|
1841
1914
|
};
|
|
1842
1915
|
switch (parsed.command) {
|
|
1843
1916
|
case "approve": {
|
|
1844
1917
|
console.log(`Approving step: ${state.currentStepName}`);
|
|
1845
1918
|
updateDocumentStatus(outputPath, "approved");
|
|
1919
|
+
if (parsed.createPR) {
|
|
1920
|
+
console.log("PR will be created after changes are committed.");
|
|
1921
|
+
}
|
|
1922
|
+
if (parsed.createIssue) {
|
|
1923
|
+
console.log("Creating/Updating Issue...");
|
|
1924
|
+
await createOrUpdateIssue(githubConfig, taskId, userQuery, state.currentStepName);
|
|
1925
|
+
}
|
|
1846
1926
|
const nextIndex = state.currentStepIndex + 1;
|
|
1847
1927
|
if (nextIndex >= config.steps.length) {
|
|
1848
1928
|
console.log("\u2705 Workflow completed!");
|
|
@@ -1865,7 +1945,7 @@ async function githubCommand(options) {
|
|
|
1865
1945
|
const platform = new GitHubPlatform(githubConfig);
|
|
1866
1946
|
const executor = new Executor({ platform, config, cwd });
|
|
1867
1947
|
try {
|
|
1868
|
-
await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex);
|
|
1948
|
+
await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex, parsed.feedback);
|
|
1869
1949
|
} catch (error) {
|
|
1870
1950
|
if (error.name !== "PauseForInputError") {
|
|
1871
1951
|
throw error;
|
|
@@ -1934,6 +2014,51 @@ async function postComment(config, body) {
|
|
|
1934
2014
|
proc.on("error", reject);
|
|
1935
2015
|
});
|
|
1936
2016
|
}
|
|
2017
|
+
async function createOrUpdateIssue(config, taskId, userQuery, stepName) {
|
|
2018
|
+
const { spawnSync } = await import("child_process");
|
|
2019
|
+
const { owner, repo, issueNumber } = config;
|
|
2020
|
+
const body = `## Spets Workflow Update
|
|
2021
|
+
|
|
2022
|
+
- Task ID: \`${taskId}\`
|
|
2023
|
+
- Current Step: **${stepName}** \u2705 Approved
|
|
2024
|
+
|
|
2025
|
+
### Description
|
|
2026
|
+
${userQuery}
|
|
2027
|
+
|
|
2028
|
+
---
|
|
2029
|
+
_Updated by [Spets](https://github.com/eatnug/spets)_`;
|
|
2030
|
+
if (issueNumber) {
|
|
2031
|
+
const result = spawnSync("gh", [
|
|
2032
|
+
"issue",
|
|
2033
|
+
"comment",
|
|
2034
|
+
String(issueNumber),
|
|
2035
|
+
"--repo",
|
|
2036
|
+
`${owner}/${repo}`,
|
|
2037
|
+
"--body",
|
|
2038
|
+
body
|
|
2039
|
+
], { encoding: "utf-8" });
|
|
2040
|
+
if (result.status !== 0) {
|
|
2041
|
+
throw new Error(`Failed to comment on issue: ${result.stderr}`);
|
|
2042
|
+
}
|
|
2043
|
+
} else {
|
|
2044
|
+
const title = userQuery.slice(0, 50) + (userQuery.length > 50 ? "..." : "");
|
|
2045
|
+
const result = spawnSync("gh", [
|
|
2046
|
+
"issue",
|
|
2047
|
+
"create",
|
|
2048
|
+
"--repo",
|
|
2049
|
+
`${owner}/${repo}`,
|
|
2050
|
+
"--title",
|
|
2051
|
+
title,
|
|
2052
|
+
"--body",
|
|
2053
|
+
body,
|
|
2054
|
+
"--label",
|
|
2055
|
+
"spets"
|
|
2056
|
+
], { encoding: "utf-8" });
|
|
2057
|
+
if (result.status !== 0) {
|
|
2058
|
+
throw new Error(`Failed to create issue: ${result.stderr}`);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
1937
2062
|
|
|
1938
2063
|
// src/orchestrator/index.ts
|
|
1939
2064
|
import { readFileSync, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|