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.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/index.js +155 -30
  3. 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
- - `ANTHROPIC_API_KEY` - Claude API
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 ANTHROPIC_API_KEY
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 ANTHROPIC_API_KEY to your repo secrets");
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
- ANTHROPIC_API_KEY: ${gh("secrets.ANTHROPIC_API_KEY")}
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
- ANTHROPIC_API_KEY: ${gh("secrets.ANTHROPIC_API_KEY")}
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("**Reply with:**");
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 here`);
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
- "```markdown",
1132
- doc,
1133
- "```",
1134
- "",
1135
- "</details>",
1136
- "",
1137
- "---",
1165
+ escapedDoc,
1138
1166
  "",
1139
- "**Commands:**",
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
- "--print",
1267
+ "-p",
1268
+ prompt,
1210
1269
  "--permission-mode",
1211
1270
  "bypassPermissions"
1212
- ], { stdio: ["pipe", "pipe", "pipe"] });
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: ${stderr}`));
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spets",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Spec Driven Development Execution Framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",