spets 0.1.4 → 0.1.5

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 +104 -16
  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,7 @@ function parseGitHubCommand(comment) {
1055
1068
  var GitHubPlatform = class extends BasePlatform {
1056
1069
  config;
1057
1070
  currentTaskId;
1071
+ currentOutputPath;
1058
1072
  constructor(config) {
1059
1073
  super();
1060
1074
  this.config = config;
@@ -1064,6 +1078,7 @@ var GitHubPlatform = class extends BasePlatform {
1064
1078
  }
1065
1079
  async generateDocument(context) {
1066
1080
  this.currentTaskId = context.taskId;
1081
+ this.currentOutputPath = context.outputPath;
1067
1082
  const prompt = this.buildPrompt(context);
1068
1083
  const response = await this.callClaude(prompt);
1069
1084
  const { document, questions } = this.parseResponse(response);
@@ -1077,7 +1092,7 @@ var GitHubPlatform = class extends BasePlatform {
1077
1092
  throw new PauseForInputError("questions", questions);
1078
1093
  }
1079
1094
  async requestApproval(doc, stepName) {
1080
- const comment = this.formatApprovalComment(doc, stepName, this.currentTaskId);
1095
+ const comment = this.formatApprovalComment(doc, stepName, this.currentTaskId, this.currentOutputPath);
1081
1096
  await this.postComment(comment);
1082
1097
  console.log("\n\u23F8\uFE0F Waiting for approval on GitHub...");
1083
1098
  console.log(" Comment /approve, /revise <feedback>, or /reject on the PR/Issue.");
@@ -1110,20 +1125,31 @@ var GitHubPlatform = class extends BasePlatform {
1110
1125
  lines.push("");
1111
1126
  }
1112
1127
  lines.push("---");
1113
- lines.push("**Reply with:**");
1128
+ lines.push("");
1129
+ lines.push("**How to answer:**");
1130
+ lines.push("");
1114
1131
  lines.push("```");
1115
1132
  lines.push("/answer");
1116
1133
  for (let i = 0; i < questions.length; i++) {
1117
- lines.push(`Q${i + 1}: your answer here`);
1134
+ lines.push(`Q${i + 1}: <your answer for question ${i + 1}>`);
1118
1135
  }
1119
1136
  lines.push("```");
1137
+ lines.push("");
1138
+ lines.push("Example:");
1139
+ lines.push("```");
1140
+ lines.push("/answer");
1141
+ lines.push("Q1: Use TypeScript");
1142
+ lines.push("Q2: Yes, add unit tests");
1143
+ lines.push("```");
1120
1144
  return lines.join("\n");
1121
1145
  }
1122
- formatApprovalComment(doc, stepName, taskId) {
1146
+ formatApprovalComment(doc, stepName, taskId, outputPath) {
1147
+ const relativePath = outputPath ? outputPath.replace(process.cwd() + "/", "") : `.spets/outputs/${taskId}/${stepName}.md`;
1123
1148
  const lines = [
1124
1149
  `## \u{1F4C4} Spets: ${stepName} - Review Required`,
1125
1150
  "",
1126
1151
  `> Task ID: \`${taskId}\``,
1152
+ `> Output: \`${relativePath}\``,
1127
1153
  "",
1128
1154
  "<details>",
1129
1155
  "<summary>\u{1F4DD} View Document</summary>",
@@ -1140,8 +1166,12 @@ var GitHubPlatform = class extends BasePlatform {
1140
1166
  "| Command | Description |",
1141
1167
  "|---------|-------------|",
1142
1168
  "| `/approve` | Approve and continue to next step |",
1169
+ "| `/approve --pr` | Approve and create a Pull Request |",
1170
+ "| `/approve --issue` | Approve and create/update an Issue |",
1143
1171
  "| `/revise <feedback>` | Request changes with feedback |",
1144
- "| `/reject` | Reject and stop workflow |"
1172
+ "| `/reject` | Reject and stop workflow |",
1173
+ "",
1174
+ "Example: `/revise Please add more details about error handling`"
1145
1175
  ];
1146
1176
  return lines.join("\n");
1147
1177
  }
@@ -1206,12 +1236,11 @@ var GitHubPlatform = class extends BasePlatform {
1206
1236
  async callClaude(prompt) {
1207
1237
  return new Promise((resolve, reject) => {
1208
1238
  const proc = spawn3("claude", [
1209
- "--print",
1239
+ "-p",
1240
+ prompt,
1210
1241
  "--permission-mode",
1211
1242
  "bypassPermissions"
1212
- ], { stdio: ["pipe", "pipe", "pipe"] });
1213
- proc.stdin.write(prompt);
1214
- proc.stdin.end();
1243
+ ], { stdio: ["ignore", "pipe", "pipe"] });
1215
1244
  let stdout = "";
1216
1245
  let stderr = "";
1217
1246
  proc.stdout.on("data", (data) => {
@@ -1222,7 +1251,7 @@ var GitHubPlatform = class extends BasePlatform {
1222
1251
  });
1223
1252
  proc.on("close", (code) => {
1224
1253
  if (code !== 0) {
1225
- reject(new Error(`Claude CLI failed: ${stderr}`));
1254
+ reject(new Error(`Claude CLI failed (code ${code}): stderr=${stderr}, stdout=${stdout}`));
1226
1255
  } else {
1227
1256
  resolve(stdout);
1228
1257
  }
@@ -1843,6 +1872,15 @@ async function githubCommand(options) {
1843
1872
  case "approve": {
1844
1873
  console.log(`Approving step: ${state.currentStepName}`);
1845
1874
  updateDocumentStatus(outputPath, "approved");
1875
+ if (parsed.createPR) {
1876
+ console.log("Creating Pull Request...");
1877
+ const prNumber = await createPullRequest(githubConfig, taskId, userQuery, state.currentStepName);
1878
+ console.log(`Created PR #${prNumber}`);
1879
+ }
1880
+ if (parsed.createIssue) {
1881
+ console.log("Creating/Updating Issue...");
1882
+ await createOrUpdateIssue(githubConfig, taskId, userQuery, state.currentStepName);
1883
+ }
1846
1884
  const nextIndex = state.currentStepIndex + 1;
1847
1885
  if (nextIndex >= config.steps.length) {
1848
1886
  console.log("\u2705 Workflow completed!");
@@ -1865,7 +1903,7 @@ async function githubCommand(options) {
1865
1903
  const platform = new GitHubPlatform(githubConfig);
1866
1904
  const executor = new Executor({ platform, config, cwd });
1867
1905
  try {
1868
- await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex);
1906
+ await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex, parsed.feedback);
1869
1907
  } catch (error) {
1870
1908
  if (error.name !== "PauseForInputError") {
1871
1909
  throw error;
@@ -1934,6 +1972,56 @@ async function postComment(config, body) {
1934
1972
  proc.on("error", reject);
1935
1973
  });
1936
1974
  }
1975
+ async function createPullRequest(config, taskId, userQuery, stepName) {
1976
+ const { execSync: execSync5 } = await import("child_process");
1977
+ const { owner, repo } = config;
1978
+ const title = userQuery.slice(0, 50) + (userQuery.length > 50 ? "..." : "");
1979
+ const body = `## Spets Workflow
1980
+
1981
+ - Task ID: \`${taskId}\`
1982
+ - Current Step: **${stepName}**
1983
+
1984
+ ### Description
1985
+ ${userQuery}
1986
+
1987
+ ---
1988
+ _Created by [Spets](https://github.com/eatnug/spets)_`;
1989
+ const result = execSync5(
1990
+ `gh pr create --repo ${owner}/${repo} --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}"`,
1991
+ { encoding: "utf-8" }
1992
+ );
1993
+ const match = result.match(/\/pull\/(\d+)/);
1994
+ if (!match) {
1995
+ throw new Error("Failed to parse PR number from gh output");
1996
+ }
1997
+ return parseInt(match[1], 10);
1998
+ }
1999
+ async function createOrUpdateIssue(config, taskId, userQuery, stepName) {
2000
+ const { execSync: execSync5 } = await import("child_process");
2001
+ const { owner, repo, issueNumber } = config;
2002
+ const body = `## Spets Workflow Update
2003
+
2004
+ - Task ID: \`${taskId}\`
2005
+ - Current Step: **${stepName}** \u2705 Approved
2006
+
2007
+ ### Description
2008
+ ${userQuery}
2009
+
2010
+ ---
2011
+ _Updated by [Spets](https://github.com/eatnug/spets)_`;
2012
+ if (issueNumber) {
2013
+ execSync5(
2014
+ `gh issue comment ${issueNumber} --repo ${owner}/${repo} --body "${body.replace(/"/g, '\\"')}"`,
2015
+ { encoding: "utf-8" }
2016
+ );
2017
+ } else {
2018
+ const title = userQuery.slice(0, 50) + (userQuery.length > 50 ? "..." : "");
2019
+ execSync5(
2020
+ `gh issue create --repo ${owner}/${repo} --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}" --label spets`,
2021
+ { encoding: "utf-8" }
2022
+ );
2023
+ }
2024
+ }
1937
2025
 
1938
2026
  // src/orchestrator/index.ts
1939
2027
  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.5",
4
4
  "description": "Spec Driven Development Execution Framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",